diff --git a/.changeset/chips-component.md b/.changeset/chips-component.md new file mode 100644 index 000000000..c3f808c1d --- /dev/null +++ b/.changeset/chips-component.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/ui': minor +--- + +Add the `Chip` component — a compact tag in the DS 2.0 solid and outline treatments, four sizes (`s`–`xl`), optional prefix icon and close affordance. diff --git a/.changeset/conditions-tag-chip.md b/.changeset/conditions-tag-chip.md new file mode 100644 index 000000000..6b9f17cde --- /dev/null +++ b/.changeset/conditions-tag-chip.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/sdk': patch +--- + +The dynamic-conditions counter tag is rendered with the UI `Chip` component. Same neutral surface colors; the tag gets the DS 2.0 chip metrics — 10px label (was 12px) in a 20px-tall pill with slightly tighter padding. diff --git a/apps/docs/scripts/ui-components.mjs b/apps/docs/scripts/ui-components.mjs index 03f4d47a1..2ca13c574 100644 --- a/apps/docs/scripts/ui-components.mjs +++ b/apps/docs/scripts/ui-components.mjs @@ -20,6 +20,7 @@ export const COMPONENTS = [ dir: 'button', }, { slug: 'checkbox', name: 'Checkbox', propsType: 'CheckboxProps', dir: 'checkbox' }, + { slug: 'chips', name: 'Chip', propsType: 'ChipProps', dir: 'chips' }, { slug: 'collapsible', name: 'Collapsible', propsType: 'CollapsibleProps', dir: 'collapsible' }, { slug: 'date-picker', name: 'DatePicker', propsType: 'DatePickerProps', dir: 'date-picker' }, { slug: 'icon-switch', name: 'IconSwitch', propsType: 'IconSwitchProps', dir: 'switch/icon-switch' }, diff --git a/apps/docs/src/components/ui-examples/chips.tsx b/apps/docs/src/components/ui-examples/chips.tsx new file mode 100644 index 000000000..31054357e --- /dev/null +++ b/apps/docs/src/components/ui-examples/chips.tsx @@ -0,0 +1,17 @@ +import { Tag } from '@phosphor-icons/react'; +import { Chip } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function ChipsExample() { + return ( + +
+ + + } /> + {}} /> +
+
+ ); +} diff --git a/apps/docs/src/content/docs/ui-library/ui-components/chips.mdx b/apps/docs/src/content/docs/ui-library/ui-components/chips.mdx new file mode 100644 index 000000000..36af95b93 --- /dev/null +++ b/apps/docs/src/content/docs/ui-library/ui-components/chips.mdx @@ -0,0 +1,33 @@ +--- +title: Chip +description: Compact tag for labeling and filtering, in solid and outline treatments. +--- + +import CssVariablesTable from '../../../../components/api/css-variables-table.astro'; +import PropsTable from '../../../../components/api/props-table.astro'; +import { ChipsExample } from '../../../../components/ui-examples/chips'; + +A `Chip` is a compact tag in one of two treatments: `solid` on the neutral +chip surface, or `outline` with the brand border and accent. Four sizes +(`s`, `m`, `l`, `xl`), an optional prefix icon, and an optional close +affordance. + + + +## Usage + +```tsx +import { Chip } from '@workflowbuilder/ui'; + +function Example() { + return remove()} />; +} +``` + +## Props + + + +## CSS variables + + diff --git a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.module.css b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.module.css index 40681cb9b..6c0231e74 100644 --- a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.module.css +++ b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.module.css @@ -2,8 +2,6 @@ --wb-conditions-form-border-color: var(--ax-ui-stroke-primary-default); /* missing token */ --wb-conditions-form-border-radius: 0.75rem; /* missing token */ --wb-conditions-form-header-color: var(--ax-public-form-label-color); - - --wb-conditions-form-tag-bg-color: var(--ax-chips-neutral-bg); } .container { @@ -27,12 +25,5 @@ } .tag { - composes: ax-public-p10 from global; - - display: inline-block; margin-top: 0.25rem; - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; - background-color: var(--wb-conditions-form-tag-bg-color); - margin-right: auto; } diff --git a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.tsx b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.tsx index ef6327bab..99beb7c6f 100644 --- a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.tsx +++ b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.tsx @@ -1,4 +1,4 @@ -import { NavButton } from '@workflowbuilder/ui'; +import { Chip, NavButton } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; @@ -49,7 +49,7 @@ function DynamicConditionsControl(props: DynamicConditionsControlProps) { - {t('totalNumber', { count: data.length })} + ); } diff --git a/packages/ui/src/components/chips/chips.module.css b/packages/ui/src/components/chips/chips.module.css new file mode 100644 index 000000000..62c4b6834 --- /dev/null +++ b/packages/ui/src/components/chips/chips.module.css @@ -0,0 +1,69 @@ +@layer ui.component { + .chip { + display: inline-flex; + align-items: center; + gap: var(--wb-space-50); + border-radius: var(--wb-radius-50); + white-space: nowrap; + } + + .solid { + background-color: var(--wb-components-chips-solid); + color: var(--wb-components-chips-solid-text); + } + + .outline { + background-color: var(--wb-components-chips-inset-outline); + background-image: linear-gradient( + var(--wb-components-chips-overlay-outline), + var(--wb-components-chips-overlay-outline) + ); + box-shadow: 0 0 0 var(--wb-size-12) var(--wb-components-chips-inset-outline); + color: var(--wb-components-chips-inset-outline); + } + + .s { + height: 0.875rem; + padding-inline: var(--wb-space-50); + } + + .m { + height: 1rem; + padding-inline: var(--wb-space-50); + } + + .l { + height: 1.25rem; + padding-inline: var(--wb-space-75); + } + + .xl { + height: 1.375rem; + padding-inline: var(--wb-space-75); + } + + .icon, + .close { + display: inline-flex; + align-items: center; + } + + .icon svg, + .close svg { + width: 0.625rem; + height: 0.625rem; + } + + .close { + padding: 0; + border: none; + background: none; + color: inherit; + cursor: pointer; + + &:focus-visible { + box-shadow: 0 0 0 2px var(--ax-focus-ring-element); + outline: none; + } + } +} diff --git a/packages/ui/src/components/chips/chips.tsx b/packages/ui/src/components/chips/chips.tsx new file mode 100644 index 000000000..001f121b8 --- /dev/null +++ b/packages/ui/src/components/chips/chips.tsx @@ -0,0 +1,51 @@ +import { X } from '@phosphor-icons/react'; +import clsx from 'clsx'; +import { HTMLAttributes, ReactNode, forwardRef } from 'react'; + +import styles from './chips.module.css'; + +export type ChipProps = { + label: string; + /** + * @default 'solid' + */ + variant?: ChipVariant; + /** + * @default 'm' + */ + size?: ChipSize; + prefixIcon?: ReactNode; + /** + * When set, a close affordance is rendered and invokes this callback. + */ + onClose?: () => void; + /** + * Accessible name of the close affordance. + * @default `Remove ${label}` + */ + closeLabel?: string; +} & HTMLAttributes; + +export type ChipVariant = 'solid' | 'outline'; +export type ChipSize = 's' | 'm' | 'l' | 'xl'; + +export const Chip = forwardRef( + ({ label, variant = 'solid', size = 'm', prefixIcon, onClose, closeLabel, className, ...rest }, ref) => { + return ( + + {prefixIcon && {prefixIcon}} + {label} + {onClose && ( + + )} + + ); + }, +); diff --git a/packages/ui/src/components/chips/index.ts b/packages/ui/src/components/chips/index.ts new file mode 100644 index 000000000..ffd127cac --- /dev/null +++ b/packages/ui/src/components/chips/index.ts @@ -0,0 +1 @@ +export * from './chips'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index d50590eba..932463d2c 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -8,6 +8,7 @@ export * from './components/accordion'; export * from './components/avatar'; export * from './components/button'; export * from './components/checkbox'; +export * from './components/chips'; export * from './components/collapsible'; export * from './components/date-picker'; export * from './components/edge'; diff --git a/packages/ui/src/styles/_provisional.css b/packages/ui/src/styles/_provisional.css index 2a5a192cb..27849bc7b 100644 --- a/packages/ui/src/styles/_provisional.css +++ b/packages/ui/src/styles/_provisional.css @@ -19,5 +19,23 @@ --wb-font-size-300: 1.5rem; --wb-font-size-350: 1.75rem; --wb-font-size-400: 2rem; + + --wb-radius-50: 0.25rem; + --wb-space-50: 0.25rem; + --wb-space-75: 0.375rem; + --wb-size-12: 0.0625rem; + --wb-components-chips-inset-outline: var(--ax-colors-acc1-500); + } + + html[data-theme='light'] { + --wb-components-chips-solid: var(--ax-colors-gray-300); + --wb-components-chips-solid-text: var(--ax-colors-gray-800); + --wb-components-chips-overlay-outline: color-mix(in srgb, var(--ax-colors-gray-100) 85%, transparent); + } + + html[data-theme='dark'] { + --wb-components-chips-solid: var(--ax-colors-gray-650); + --wb-components-chips-solid-text: var(--ax-colors-gray-100); + --wb-components-chips-overlay-outline: color-mix(in srgb, var(--ax-colors-gray-900) 75%, transparent); } } diff --git a/packages/ui/vite.config.mts b/packages/ui/vite.config.mts index dfc38300b..daf4d3cd3 100644 --- a/packages/ui/vite.config.mts +++ b/packages/ui/vite.config.mts @@ -19,6 +19,7 @@ const componentEntries = [ 'avatar', 'button', 'checkbox', + 'chips', 'collapsible', 'date-picker', 'edge',