diff --git a/apps/www/content/primitives/components/chip.mdx b/apps/www/content/primitives/components/chip.mdx new file mode 100644 index 00000000..65fb48b6 --- /dev/null +++ b/apps/www/content/primitives/components/chip.mdx @@ -0,0 +1,240 @@ +--- +title: Chip +description: A compact element for representing an input, attribute, or action. +--- + +## Preview + + + + Default + + Accent + + With Icon + + alert("Dismiss clicked")} + >Dismissible + + + + +## Usage + +The Chip component is used to represent attributes, tags, or actions in a compact form. It supports various styles, sizes, and can include leading/trailing icons or a dismiss button. + +## Installation + +Install the component from your command line. + + + + + + + Label`} border/> + + +## Chip Props + +The `Chip` component accepts the following props: + +- `variant`: Visual style variant (`"outline"` | `"filled"`, default: "outline") +- `size`: Size of the chip (`"small"` | `"large"`, default: "small") +- `style`: Color style (`"neutral"` | `"accent"`, default: "neutral") +- `leadingIcon`: ReactNode to display as an icon before the label +- `trailingIcon`: ReactNode to display as an icon after the label +- `isDismissible`: Boolean to show a dismiss button (replaces trailingIcon) +- `onDismiss`: Callback function when dismiss button is clicked +- `children`: Content to display inside the chip +- `className`: Additional CSS class names +- `role`: ARIA role for the chip (default: "status") +- `ariaLabel`: Custom accessibility label for the chip + + +## Variants + +Choose between outline and filled variants to match your design needs. + + + Default + Accent + With Icon + Dismissible + `, + }, + { + name: "Filled", + code: ` +
+ Default + Accent + With Icon + Dismissible +
`, + }, + ]} +/> + +## Sizes + +The Chip component comes in two sizes: +- `small`: Compact size with less padding +- `large`: More spacious with increased padding + + + Small Chip + Small Filled + With Icon + Dismissible + `, + }, + { + name: "Large", + code: ` +
+ Large Chip + Large Filled + With Icon + Dismissible +
`, + }, + ]} +/> + +## Styles + +Choose between neutral and accent styles to control the visual emphasis. + + + Outline + Filled + With Icon + Dismissible + `, + }, + { + name: "Accent", + code: ` +
+ Outline + Filled + With Icon + Dismissible +
`, + }, + ]} +/> + +## With Icons + +Chips can include leading and trailing icons to provide additional context or actions. + + + Add Item + Selected + `, + }, + { + name: "Trailing Icon", + code: ` +
+ Next + Open +
`, + }, + { + name: "Both Icons", + code: ` +
+ Download + Edit Profile +
`, + }, + ]} +/> + +## Dismissible + +Chips can be made dismissible by adding the isDismissible prop and an onDismiss handler. + + console.log('dismissed')} + ariaLabel="Dismissible chip" + > + Click to Dismiss + `, + }, + { + name: "With Styles", + code: ` +
+ console.log('dismissed')} + > + Accent Outline + + console.log('dismissed')} + > + Accent Filled + +
`, + }, + ]} +/> + + +## Accessibility + +The Chip component has some accessibility features: + +- Uses semantic HTML elements +- Includes proper ARIA roles and labels +- Provides keyboard navigation support +- Makes decorative elements hidden from screen readers +- Ensures proper contrast ratios in all variants and states diff --git a/apps/www/examples/shield-ts/assets.tsx b/apps/www/examples/shield-ts/assets.tsx index 7d7f07c6..0599a96d 100644 --- a/apps/www/examples/shield-ts/assets.tsx +++ b/apps/www/examples/shield-ts/assets.tsx @@ -1,13 +1,14 @@ import React, { useState, useCallback, useEffect } from "react"; import dayjs from "dayjs"; -import { HomeIcon } from "@radix-ui/react-icons"; +import { HomeIcon, Cross1Icon, PlusIcon, CheckIcon } from "@radix-ui/react-icons"; import { DataTable, Title, useTable } from "@raystack/apsara"; -import { toast, ToastContainer, Avatar, AvatarGroup, Button, Spinner, DropdownMenu, Breadcrumb, Flex, Text, Checkbox, InputField, Badge } from "@raystack/apsara/v1"; +import { toast, ToastContainer, Avatar, AvatarGroup, Button, Spinner, DropdownMenu, Breadcrumb, Chip, Flex, Text, Checkbox, InputField, Badge } from "@raystack/apsara/v1"; + import { getData, Payment } from "./data"; import { ApsaraColumnDef } from "@raystack/apsara/table/datatables.types"; const TOTAL_PAGES = 100; @@ -265,13 +266,50 @@ const AssetsHeader = () => { /> */} + {/* Add Chip examples */} + + } trailingIcon={}>Default + + {/* } + > + Large Accent + + + } + > + With Icon + + + console.log('dismissed')} + > + Dismissible + + + } + trailingIcon={} + > + Both Icons + */} + + - + {/* {isFiltered ? : } - + */} ); }; diff --git a/apps/www/utils/routes.ts b/apps/www/utils/routes.ts index 1a983593..b2df35d5 100644 --- a/apps/www/utils/routes.ts +++ b/apps/www/utils/routes.ts @@ -31,6 +31,7 @@ export const primitivesRoutes = [ { title: "Calendar", slug: "docs/primitives/components/calendar" }, { title: "Command", slug: "docs/primitives/components/command" }, { title: "Checkbox", slug: "docs/primitives/components/checkbox", newBadge: true }, + { title: "Chip", slug: "docs/primitives/components/chip", newBadge: true }, { title: "Container", slug: "docs/primitives/components/container" }, { title: "Datatable", slug: "docs/primitives/components/datatable" }, { title: "Dialog", slug: "docs/primitives/components/dialog" }, diff --git a/packages/raystack/v1/components/chip/chip.module.css b/packages/raystack/v1/components/chip/chip.module.css new file mode 100644 index 00000000..a8809ca0 --- /dev/null +++ b/packages/raystack/v1/components/chip/chip.module.css @@ -0,0 +1,116 @@ +.chip { + display: inline-flex; + justify-content: center; + align-items: center; + gap: var(--rs-space-2); + font-size: 11px; + font-style: normal; + font-weight: 400; + border-radius: var(--rs-radius-2); + white-space: nowrap; + cursor: default; + transition: all 0.2s ease; + box-sizing: border-box; + height: fit-content; + width: fit-content; +} + +/* Variants */ +.chip-variant-outline { + background: transparent; +} + +.chip-variant-filled { + background: var(--rs-color-background-base-primary); +} + +/* Sizes */ +.chip-size-large { + padding: var(--rs-space-2) var(--rs-space-3); +} + +.chip-size-small { + padding: var(--rs-space-1) var(--rs-space-2); +} + +/* Styles - Neutral */ +.chip-style-neutral.chip-variant-outline { + border: 0.5px solid var(--rs-color-border-base-secondary); + color: var(--rs-color-text-base-secondary); +} + +.chip-style-neutral.chip-variant-outline:hover { + color: var(--rs-color-text-base-primary); +} + +.chip-style-neutral.chip-variant-outline:active { + border: 1px solid var(--rs-color-border-base-emphasis); + color: var(--rs-color-text-base-primary); +} + +.chip-style-neutral.chip-variant-filled { + background: var(--rs-color-background-base-primary); + border: 0.5px solid var(--rs-color-border-base-secondary); + color: var(--rs-color-text-base-secondary); +} + +.chip-style-neutral.chip-variant-filled:hover { + border-color: var(--rs-color-text-base-primary); + color: var(--rs-color-text-base-primary); +} + +.chip-style-neutral.chip-variant-filled:active { + background: var(--rs-color-background-neutral-emphasis); + color: var(--rs-color-text-base-emphasis); +} + +/* Styles - Accent */ +.chip-style-accent.chip-variant-outline { + border: 0.5px solid var(--rs-color-border-accent-primary); + color: var(--rs-color-text-accent-primary); +} + +.chip-style-accent.chip-variant-outline:hover { + border-color: var(--rs-color-border-accent-emphasis); + color: var(--rs-color-text-accent-primary-hover); +} + +.chip-style-accent.chip-variant-outline:active { + border: 1px solid var(--rs-color-border-accent-emphasis); + color: var(--rs-color-text-accent-primary); +} + +.chip-style-accent.chip-variant-filled { + background: var(--rs-color-background-base-primary); + border: 0.5px solid var(--rs-color-border-accent-primary); + color: var(--rs-color-text-accent-primary); +} + +.chip-style-accent.chip-variant-filled:hover { + border-color: var(--rs-color-border-accent-emphasis); +} + +.chip-style-accent.chip-variant-filled:active { + background: var(--rs-color-background-accent-emphasis); + color: var(--rs-color-text-base-emphasis); +} + +/* Icons */ +.leading-icon, .trailing-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 12px; + height: 12px; +} + +.dismiss-button { + background: none; + border: none; + padding: 0; + cursor: pointer; + color: inherit; + display: inline-flex; + align-items: center; + justify-content: center; +} \ No newline at end of file diff --git a/packages/raystack/v1/components/chip/chip.tsx b/packages/raystack/v1/components/chip/chip.tsx new file mode 100644 index 00000000..33289857 --- /dev/null +++ b/packages/raystack/v1/components/chip/chip.tsx @@ -0,0 +1,110 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import { ReactNode } from "react"; + +import styles from "./chip.module.css"; + +const chip = cva(styles.chip, { + variants: { + variant: { + outline: styles["chip-variant-outline"], + filled: styles["chip-variant-filled"], + }, + size: { + large: styles["chip-size-large"], + small: styles["chip-size-small"], + }, + style: { + neutral: styles["chip-style-neutral"], + accent: styles["chip-style-accent"], + }, + }, + defaultVariants: { + variant: "outline", + size: "small", + style: "neutral" + }, +}); + +type ChipProps = VariantProps & { + trailingIcon?: ReactNode; + leadingIcon?: ReactNode; + isDismissible?: boolean; + children: ReactNode; + className?: string; + onDismiss?: () => void; + role?: string; + ariaLabel?: string; +} + +export const Chip = ({ + variant, + size, + style, + trailingIcon, + leadingIcon, + isDismissible, + children, + className, + onDismiss, + role = "status", + ariaLabel, +}: ChipProps) => { + const handleDismiss = (e: React.MouseEvent) => { + e.stopPropagation(); + onDismiss?.(); + }; + + return ( + + {leadingIcon && ( + + )} + {children} + {isDismissible ? ( + + ) : trailingIcon ? ( + + ) : null} + + ); +}; + +Chip.displayName = "Chip"; \ No newline at end of file diff --git a/packages/raystack/v1/components/chip/index.tsx b/packages/raystack/v1/components/chip/index.tsx new file mode 100644 index 00000000..cc4880a6 --- /dev/null +++ b/packages/raystack/v1/components/chip/index.tsx @@ -0,0 +1 @@ +export { Chip } from './chip' \ No newline at end of file diff --git a/packages/raystack/v1/components/inputField/inputField.tsx b/packages/raystack/v1/components/inputField/inputField.tsx index 50ba221c..f741aaf1 100644 --- a/packages/raystack/v1/components/inputField/inputField.tsx +++ b/packages/raystack/v1/components/inputField/inputField.tsx @@ -1,5 +1,5 @@ -import { ComponentPropsWithoutRef, ElementRef, forwardRef, ReactNode, useEffect, useRef, useState } from "react"; import clsx from "clsx"; +import { ComponentPropsWithoutRef, ElementRef, forwardRef, ReactNode, useEffect, useRef, useState } from "react"; import styles from "./inputField.module.css"; diff --git a/packages/raystack/v1/index.tsx b/packages/raystack/v1/index.tsx index 9b25a393..34efe8e5 100644 --- a/packages/raystack/v1/index.tsx +++ b/packages/raystack/v1/index.tsx @@ -5,6 +5,7 @@ export { Box } from "./components/box" export { Breadcrumb } from "./components/breadcrumb"; export { Button } from "./components/button"; export { Checkbox } from "./components/checkbox"; +export { Chip } from './components/chip' export { DropdownMenu } from "./components/dropdownMenu"; export { EmptyState } from "./components/emptyState"; export { Flex } from "./components/flex";