Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 47 additions & 23 deletions .specs/chip.md

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@ const meta = {
docs: {
description: {
component:
'A compact, dismissible token that labels a user-applied value, such as a removable filter on a data view. When `removable` is set, it renders a trailing button that emits the `remove` event; when `clickable` is set, the chip body becomes interactive and emits the `click` event.'
'A compact, pill-shaped token that labels a user-applied value, such as a filter on a data view. `kind` covers the three jobs a chip does in a filter surface: `filled` is a value that IS applied, `outlined` one the user COULD apply, and `dashed` the control that adds one. When `removable` is set, it renders a trailing button that emits `remove` — immediately, without hiding itself, so presence stays the consumer\'s call; when `clickable` is set, the chip body becomes interactive and emits `click`.'
},
canvas: { sourceState: 'shown' }
}
},
argTypes: {
kind: {
control: 'inline-radio',
options: ['filled', 'outlined', 'dashed'],
description:
'Visual variant. Filled is an applied value, outlined an available one, dashed the control that adds one.',
table: {
category: 'props',
type: { summary: "'filled' | 'outlined' | 'dashed'" },
defaultValue: { summary: 'filled' }
}
},
label: {
control: 'text',
description: 'Fallback text when the default slot is empty.',
Expand All @@ -27,7 +38,7 @@ const meta = {
size: {
control: 'inline-radio',
options: ['small', 'medium'],
description: 'Size token; medium is 24px tall, small is 20px.',
description: 'Size token; medium is a fixed 32px, small a fixed 24px.',
table: {
category: 'props',
type: { summary: "'small' | 'medium'" },
Expand All @@ -46,7 +57,7 @@ const meta = {
table: { category: 'props', type: { summary: 'boolean' }, defaultValue: { summary: 'false' } }
}
},
args: { label: 'Label', size: 'medium', removable: false, clickable: false }
args: { label: 'Label', kind: 'filled', size: 'medium', removable: false, clickable: false }
}

export default meta
Expand All @@ -59,7 +70,7 @@ const Template = (args) => ({
template: '<Chip v-bind="props" />'
})

const DEFAULT_MARKUP = '<Chip label="Label" size="medium" />'
const DEFAULT_MARKUP = '<Chip label="Label" kind="filled" size="medium" />'

export const Default = {
render: Template,
Expand All @@ -71,6 +82,29 @@ export const Default = {
}
}

const TYPES_TEMPLATE = `<div class="flex flex-wrap items-center gap-4">
<Chip label="Production" kind="filled" size="medium" removable />
<Chip label="Status" kind="outlined" size="medium" clickable />
<Chip label="Add filter" kind="dashed" size="medium" clickable />
</div>`

export const Types = {
render: () => ({
components: { Chip },
template: TYPES_TEMPLATE
}),
parameters: {
docs: {
controls: { disable: true },
description: {
story:
'The three kinds in the order they read in a filter surface: an applied value, an available one, and the control that adds one.'
},
source: { code: toSfc(IMPORT, TYPES_TEMPLATE) }
}
}
}

const SIZES_TEMPLATE = `<div class="flex flex-wrap items-center gap-4">
<Chip label="Small" size="small" />
<Chip label="Medium" size="medium" />
Expand All @@ -90,21 +124,24 @@ export const Sizes = {
}
}

const REMOVABLE_MARKUP = '<Chip label="Label" size="medium" removable />'
const REMOVABLE_MARKUP = '<Chip label="Label" kind="filled" size="medium" removable />'

export const Removable = {
args: { removable: true },
render: Template,
argTypes: { onRemove: { action: 'remove' } },
parameters: {
docs: {
description: { story: 'Removable chip; the × button emits `remove`.' },
description: {
story:
'Removable chip; the × button emits `remove` immediately. The chip does not hide itself — the consumer decides whether it disappears, stays, or becomes an `outlined` offer.'
},
source: { code: toSfc(IMPORT, REMOVABLE_MARKUP) }
}
}
}

const CLICKABLE_MARKUP = '<Chip label="Label" size="medium" clickable />'
const CLICKABLE_MARKUP = '<Chip label="Label" kind="filled" size="medium" clickable />'

export const Clickable = {
args: { clickable: true },
Expand Down
5 changes: 5 additions & 0 deletions packages/webkit/.size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@
"name": "password-requirements",
"path": "src/components/inputs/field-password/password-requirements.ts",
"limit": "0.3 KB"
},
{
"name": "chip",
"path": "src/components/inputs/chip/chip.vue",
"limit": "5 KB"
}
]
18 changes: 14 additions & 4 deletions packages/webkit/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -6359,12 +6359,19 @@
"required": "false",
"doc": "Fallback text when the default slot is empty."
},
{
"name": "kind",
"type": "'filled' | 'outlined' | 'dashed'",
"default": "'filled'",
"required": "false",
"doc": "Visual variant. Filled is an applied value, outlined an available one, dashed the control that adds one."
},
{
"name": "size",
"type": "'small' | 'medium'",
"default": "'medium'",
"required": "false",
"doc": "Size token; `small` is a fixed 20px, `medium`'s height is driven by its vertical padding (~30px)."
"doc": "Size token; `small` is a fixed 24px, `medium` a fixed 32px."
},
{
"name": "removable",
Expand All @@ -6385,7 +6392,7 @@
{
"name": "remove",
"payload": "(event: MouseEvent, label: string)",
"notes": "Fires when `removable` is true and the remove button is activated (click / Enter / Space), after the chip's exit (fade-out) animation completes, so the parent removes the chip once it has animated out. `label` is the chip's `label` prop, identifying which chip was removed."
"notes": "Fires when `removable` is true and the remove button is activated (click / Enter / Space), **immediately** — the chip does not hide or unmount itself first, so the consumer decides whether the chip disappears, stays, or becomes an `outlined` offer. `label` is the chip's `label` prop, identifying which chip was removed."
},
{
"name": "click",
Expand All @@ -6400,9 +6407,11 @@
"notes": "Chip content; falls back to `label` when empty."
}
],
"purpose": "A Chip is a compact, self-contained token that labels a discrete value the user has applied — most often a removable filter on a data view. Unlike `tag` (a read-only status/category badge with severity color coding), a Chip carries no severity and exposes an optional trailing remove control: when `removable` is set, it renders a real `<button>` that emits `remove`, so the consumer can drop the value from a collection. This is the token consumed by the data table's `#filters` slot (`<Chip :label=\"f.label\" removable @remove=\"dropFilter(f)\" />`): each active filter becomes one removable Chip, and dismissing it removes that filter. Reach for a Chip when a value is user-applied and dismissible; reach for `tag` when it is a static status or category indicator.",
"purpose": "A Chip is a compact, pill-shaped token that labels a discrete value the user has applied — most often a filter on a data view. Unlike `tag` (a read-only status/category badge with severity color coding), a Chip carries no severity and exposes an optional trailing remove control: when `removable` is set, it renders a real `<button>` that emits `remove`, so the consumer can drop the value from a collection. This is the token consumed by the data table's `#filters` slot (`<Chip :label=\"f.label\" removable @remove=\"dropFilter(f)\" />`): each active filter becomes one removable Chip, and dismissing it removes that filter. Reach for a Chip when a value is user-applied and dismissible; reach for `tag` when it is a static status or category indicator. `kind` covers the three jobs a chip does in a filter surface, so a consumer never restyles the component to get one of them: - **`filled`** — a value that IS applied. The shared surface and border plus `--shadow-sm`: the loudest of the three, because it is state. - **`outlined`** — a value the user COULD apply. Same surface and border, no shadow: it recedes to an offer without becoming disabled, so a filter vocabulary can stay on screen instead of hiding inside a menu. - **`dashed`** — the control that CREATES a chip (\"Add filter\"). A dashed outline is the standing convention for \"add another one of these\", which is what separates the thing that makes filters from the things that are filters. **Presence is the consumer's, not the chip's.** `remove` fires the moment the control is activated; the chip does not animate itself away or unmount itself. A chip that hid itself on remove could only ever serve the one case where removing it also destroys it — a filter bar where a removed value stays on screen as an `outlined` offer was impossible, because the instance stayed invisible forever. Exit motion, if wanted, belongs to whatever owns the list (a `TransitionGroup` around the group).",
"useWhen": [
"Represent a selectable or removable token — active filters, multi-select values, entered keywords.",
"Represent a value that is *available* but not applied (`kind=\"outlined\"`), so the set of things a view can be narrowed by stays visible.",
"Represent the \"add another\" affordance for such a set (`kind=\"dashed\"`).",
"The element is interactive (clickable / dismissible)."
],
"avoidWhen": [
Expand All @@ -6411,7 +6420,8 @@
],
"related": [
"badge` — static count/short-value indicator.",
"tag` — static labelled status/category chip."
"tag` — static labelled status/category chip.",
"tooltip` — composed by the remove control to name what it removes."
],
"bestPractices": [
"When removable, expose an accessible remove control with a clear label.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,22 @@
</script>

<template>
<div
<!-- EXIT MOTION LIVES HERE, not in the Chip. `Chip` emits `remove` and stops; it does
not hide or unmount itself, because a chip that did could only ever serve the case
where removing it also destroys it. This band IS that case — dropping a filter
removes it from the table's state, so the chip really does go — which makes this
the right owner for the fade. Leaving chips are taken out of flow so the survivors
can slide into the gap instead of jumping. -->
<TransitionGroup
v-bind="$attrs"
tag="div"
:data-testid="testId"
class="flex flex-wrap items-center gap-(--spacing-xs)"
move-class="transition-[transform,translate,opacity] duration-moderate-01 ease-productive-entrance motion-reduce:transition-none"
enter-from-class="scale-95 opacity-0"
enter-active-class="transition-[transform,translate,scale,opacity] duration-fast-02 ease-productive-entrance motion-reduce:transition-none"
leave-to-class="scale-95 opacity-0"
leave-active-class="absolute transition-[transform,translate,scale,opacity] duration-fast-02 ease-productive-exit motion-reduce:transition-none"
>
<template
v-for="filter in filters"
Expand All @@ -91,5 +103,5 @@
</Chip>
</slot>
</template>
</div>
</TransitionGroup>
</template>
Loading
Loading