premium-ds 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/alert.d.ts +31 -0
- package/dist/alert.js +6 -0
- package/dist/alert.js.map +1 -0
- package/dist/avatar-group.d.ts +13 -0
- package/dist/avatar-group.js +3 -0
- package/dist/avatar-group.js.map +1 -0
- package/dist/avatar.d.ts +25 -0
- package/dist/avatar.js +3 -0
- package/dist/avatar.js.map +1 -0
- package/dist/badge.d.ts +23 -0
- package/dist/badge.js +3 -0
- package/dist/badge.js.map +1 -0
- package/dist/button.d.ts +20 -0
- package/dist/button.js +3 -0
- package/dist/button.js.map +1 -0
- package/dist/checkbox.d.ts +25 -0
- package/dist/checkbox.js +3 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-2OWHZ4JT.js +36 -0
- package/dist/chunk-2OWHZ4JT.js.map +1 -0
- package/dist/chunk-34SIXSYL.js +64 -0
- package/dist/chunk-34SIXSYL.js.map +1 -0
- package/dist/chunk-37O2ZXD6.js +55 -0
- package/dist/chunk-37O2ZXD6.js.map +1 -0
- package/dist/chunk-4AZL76UJ.js +89 -0
- package/dist/chunk-4AZL76UJ.js.map +1 -0
- package/dist/chunk-4HSCN5TZ.js +86 -0
- package/dist/chunk-4HSCN5TZ.js.map +1 -0
- package/dist/chunk-5DDOOT33.js +258 -0
- package/dist/chunk-5DDOOT33.js.map +1 -0
- package/dist/chunk-5FVHWIMY.js +117 -0
- package/dist/chunk-5FVHWIMY.js.map +1 -0
- package/dist/chunk-5K6KRJGX.js +147 -0
- package/dist/chunk-5K6KRJGX.js.map +1 -0
- package/dist/chunk-5PQMQBQC.js +74 -0
- package/dist/chunk-5PQMQBQC.js.map +1 -0
- package/dist/chunk-7OCTVQ7C.js +95 -0
- package/dist/chunk-7OCTVQ7C.js.map +1 -0
- package/dist/chunk-7OPMOET7.js +39 -0
- package/dist/chunk-7OPMOET7.js.map +1 -0
- package/dist/chunk-BXXS7YRC.js +270 -0
- package/dist/chunk-BXXS7YRC.js.map +1 -0
- package/dist/chunk-CV2Q4YXX.js +272 -0
- package/dist/chunk-CV2Q4YXX.js.map +1 -0
- package/dist/chunk-EIMMDWIW.js +282 -0
- package/dist/chunk-EIMMDWIW.js.map +1 -0
- package/dist/chunk-EZ2CWTBE.js +230 -0
- package/dist/chunk-EZ2CWTBE.js.map +1 -0
- package/dist/chunk-FGHDG3Y4.js +89 -0
- package/dist/chunk-FGHDG3Y4.js.map +1 -0
- package/dist/chunk-FPP2XLKX.js +127 -0
- package/dist/chunk-FPP2XLKX.js.map +1 -0
- package/dist/chunk-G6OY35DI.js +295 -0
- package/dist/chunk-G6OY35DI.js.map +1 -0
- package/dist/chunk-H6KWJNOE.js +65 -0
- package/dist/chunk-H6KWJNOE.js.map +1 -0
- package/dist/chunk-HGILYGY3.js +45 -0
- package/dist/chunk-HGILYGY3.js.map +1 -0
- package/dist/chunk-I3BCB4Z5.js +88 -0
- package/dist/chunk-I3BCB4Z5.js.map +1 -0
- package/dist/chunk-KBWNUUWM.js +582 -0
- package/dist/chunk-KBWNUUWM.js.map +1 -0
- package/dist/chunk-KN7JFAZ6.js +113 -0
- package/dist/chunk-KN7JFAZ6.js.map +1 -0
- package/dist/chunk-MEF7PI6U.js +16 -0
- package/dist/chunk-MEF7PI6U.js.map +1 -0
- package/dist/chunk-NKGMQL6I.js +310 -0
- package/dist/chunk-NKGMQL6I.js.map +1 -0
- package/dist/chunk-NMFQRGLL.js +127 -0
- package/dist/chunk-NMFQRGLL.js.map +1 -0
- package/dist/chunk-OUBWD6CX.js +433 -0
- package/dist/chunk-OUBWD6CX.js.map +1 -0
- package/dist/chunk-PFNXVBLU.js +96 -0
- package/dist/chunk-PFNXVBLU.js.map +1 -0
- package/dist/chunk-PUPZ4HME.js +165 -0
- package/dist/chunk-PUPZ4HME.js.map +1 -0
- package/dist/chunk-QFS52OK5.js +690 -0
- package/dist/chunk-QFS52OK5.js.map +1 -0
- package/dist/chunk-QNC6O3PG.js +45 -0
- package/dist/chunk-QNC6O3PG.js.map +1 -0
- package/dist/chunk-QUHOXWBK.js +82 -0
- package/dist/chunk-QUHOXWBK.js.map +1 -0
- package/dist/chunk-UIQGSTBJ.js +106 -0
- package/dist/chunk-UIQGSTBJ.js.map +1 -0
- package/dist/chunk-UJQKVP6V.js +193 -0
- package/dist/chunk-UJQKVP6V.js.map +1 -0
- package/dist/chunk-VVPGEAC6.js +11 -0
- package/dist/chunk-VVPGEAC6.js.map +1 -0
- package/dist/chunk-XA3T5KWA.js +58 -0
- package/dist/chunk-XA3T5KWA.js.map +1 -0
- package/dist/chunk-YSHJHSJM.js +19 -0
- package/dist/chunk-YSHJHSJM.js.map +1 -0
- package/dist/chunk-YVHOAVSM.js +182 -0
- package/dist/chunk-YVHOAVSM.js.map +1 -0
- package/dist/collapse.d.ts +16 -0
- package/dist/collapse.js +3 -0
- package/dist/collapse.js.map +1 -0
- package/dist/count-badge.d.ts +11 -0
- package/dist/count-badge.js +4 -0
- package/dist/count-badge.js.map +1 -0
- package/dist/date-field.d.ts +39 -0
- package/dist/date-field.js +8 -0
- package/dist/date-field.js.map +1 -0
- package/dist/date-range-field.d.ts +30 -0
- package/dist/date-range-field.js +8 -0
- package/dist/date-range-field.js.map +1 -0
- package/dist/datetime-field.d.ts +28 -0
- package/dist/datetime-field.js +10 -0
- package/dist/datetime-field.js.map +1 -0
- package/dist/dialog.d.ts +26 -0
- package/dist/dialog.js +7 -0
- package/dist/dialog.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/motion-tokens.d.ts +29 -0
- package/dist/motion-tokens.js +3 -0
- package/dist/motion-tokens.js.map +1 -0
- package/dist/multi-select.d.ts +25 -0
- package/dist/multi-select.js +7 -0
- package/dist/multi-select.js.map +1 -0
- package/dist/number-field.d.ts +24 -0
- package/dist/number-field.js +4 -0
- package/dist/number-field.js.map +1 -0
- package/dist/otp-field.d.ts +20 -0
- package/dist/otp-field.js +3 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/overlay.d.ts +31 -0
- package/dist/overlay.js +4 -0
- package/dist/overlay.js.map +1 -0
- package/dist/pagination.d.ts +24 -0
- package/dist/pagination.js +5 -0
- package/dist/pagination.js.map +1 -0
- package/dist/radio-group.d.ts +46 -0
- package/dist/radio-group.js +6 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/select-core-SAyS-8w0.d.ts +16 -0
- package/dist/select.d.ts +27 -0
- package/dist/select.js +7 -0
- package/dist/select.js.map +1 -0
- package/dist/status-badge.d.ts +17 -0
- package/dist/status-badge.js +5 -0
- package/dist/status-badge.js.map +1 -0
- package/dist/table.d.ts +65 -0
- package/dist/table.js +5 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.d.ts +44 -0
- package/dist/tabs.js +5 -0
- package/dist/tabs.js.map +1 -0
- package/dist/tag.d.ts +28 -0
- package/dist/tag.js +5 -0
- package/dist/tag.js.map +1 -0
- package/dist/text-field.d.ts +30 -0
- package/dist/text-field.js +6 -0
- package/dist/text-field.js.map +1 -0
- package/dist/textarea.d.ts +33 -0
- package/dist/textarea.js +5 -0
- package/dist/textarea.js.map +1 -0
- package/dist/time-field.d.ts +27 -0
- package/dist/time-field.js +6 -0
- package/dist/time-field.js.map +1 -0
- package/dist/toast-store.d.ts +75 -0
- package/dist/toast-store.js +3 -0
- package/dist/toast-store.js.map +1 -0
- package/dist/toast.d.ts +3 -0
- package/dist/toast.js +6 -0
- package/dist/toast.js.map +1 -0
- package/dist/toggle-tag.d.ts +24 -0
- package/dist/toggle-tag.js +4 -0
- package/dist/toggle-tag.js.map +1 -0
- package/dist/toggle.d.ts +21 -0
- package/dist/toggle.js +3 -0
- package/dist/toggle.js.map +1 -0
- package/dist/tooltip.d.ts +27 -0
- package/dist/tooltip.js +4 -0
- package/dist/tooltip.js.map +1 -0
- package/llms.txt +165 -0
- package/package.json +205 -0
- package/src/components/alert/Alert.tsx +118 -0
- package/src/components/alert/alert.css +136 -0
- package/src/components/avatar/Avatar.tsx +128 -0
- package/src/components/avatar/AvatarGroup.tsx +50 -0
- package/src/components/avatar/avatar.css +200 -0
- package/src/components/badge/Badge.tsx +66 -0
- package/src/components/badge/CountBadge.tsx +46 -0
- package/src/components/badge/StatusBadge.tsx +132 -0
- package/src/components/badge/badge.css +243 -0
- package/src/components/button/Button.tsx +68 -0
- package/src/components/button/button.css +222 -0
- package/src/components/checkbox/Checkbox.tsx +90 -0
- package/src/components/checkbox/checkbox.css +179 -0
- package/src/components/date-picker/DateField.tsx +362 -0
- package/src/components/date-picker/DateRangeField.tsx +533 -0
- package/src/components/date-picker/DateTimeField.tsx +177 -0
- package/src/components/date-picker/TimeField.tsx +100 -0
- package/src/components/date-picker/date-picker.css +591 -0
- package/src/components/date-picker/date-utils.ts +55 -0
- package/src/components/date-picker/field-shell.tsx +78 -0
- package/src/components/date-picker/glide-pill.tsx +81 -0
- package/src/components/date-picker/time-core.tsx +305 -0
- package/src/components/dialog/Dialog.tsx +181 -0
- package/src/components/dialog/dialog.css +170 -0
- package/src/components/glass/glass.css +100 -0
- package/src/components/icon/Icon.tsx +76 -0
- package/src/components/icon/IconSlot.tsx +11 -0
- package/src/components/icon/icon.css +33 -0
- package/src/components/input/NumberField.tsx +117 -0
- package/src/components/input/OtpField.tsx +118 -0
- package/src/components/input/TextField.tsx +123 -0
- package/src/components/input/input.css +335 -0
- package/src/components/motion/Collapse.tsx +33 -0
- package/src/components/motion/collapse.css +41 -0
- package/src/components/overlay/Overlay.tsx +239 -0
- package/src/components/overlay/overlay-core.tsx +565 -0
- package/src/components/overlay/overlay.css +119 -0
- package/src/components/overlay/sheet-drag.tsx +146 -0
- package/src/components/pagination/Pagination.tsx +140 -0
- package/src/components/pagination/pagination.css +48 -0
- package/src/components/radio-group/RadioGroup.tsx +182 -0
- package/src/components/radio-group/radio-group.css +277 -0
- package/src/components/select/MultiSelect.tsx +251 -0
- package/src/components/select/Select.tsx +235 -0
- package/src/components/select/select-core.tsx +417 -0
- package/src/components/select/select.css +386 -0
- package/src/components/table/Table.tsx +433 -0
- package/src/components/table/table.css +348 -0
- package/src/components/tabs/Tabs.tsx +371 -0
- package/src/components/tabs/tabs.css +228 -0
- package/src/components/tag/Tag.tsx +145 -0
- package/src/components/tag/ToggleTag.tsx +125 -0
- package/src/components/tag/tag.css +248 -0
- package/src/components/textarea/Textarea.tsx +197 -0
- package/src/components/textarea/textarea.css +219 -0
- package/src/components/toast/Toast.tsx +349 -0
- package/src/components/toast/toast-store.ts +266 -0
- package/src/components/toast/toast.css +233 -0
- package/src/components/toggle/Toggle.tsx +94 -0
- package/src/components/toggle/toggle.css +152 -0
- package/src/components/tooltip/Tooltip.tsx +365 -0
- package/src/components/tooltip/tooltip.css +86 -0
- package/src/index.ts +42 -0
- package/src/styles.css +39 -0
- package/src/tokens/avatar.css +20 -0
- package/src/tokens/color.css +56 -0
- package/src/tokens/elevation.css +20 -0
- package/src/tokens/fonts.css +3 -0
- package/src/tokens/glass.css +21 -0
- package/src/tokens/icons.css +7 -0
- package/src/tokens/layers.css +6 -0
- package/src/tokens/motion-tokens.ts +72 -0
- package/src/tokens/motion.css +49 -0
- package/src/tokens/radius.css +11 -0
- package/src/tokens/semantic.css +75 -0
- package/src/tokens/spacing.css +26 -0
- package/src/tokens/typography.css +54 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/* Tag (+ TagGroup) - removable/editable label; stateless, the parent owns the list. */
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
7
|
+
import { UIMotion } from '../../tokens/motion-tokens';
|
|
8
|
+
import { IconSlot } from '../icon/IconSlot';
|
|
9
|
+
|
|
10
|
+
/** Tag - the removable, editable label (stateless; the parent owns the list). */
|
|
11
|
+
export interface TagProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {
|
|
12
|
+
/** The label. */
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
/** Your own icon node, sized small and tinted to the tag's text. */
|
|
15
|
+
icon?: React.ReactNode | null;
|
|
16
|
+
/** Presence adds the remove button; called on click - remove the item yourself. */
|
|
17
|
+
onRemove?: (() => void) | null;
|
|
18
|
+
/** Accessible label for the remove button. Defaults to "Remove {label}" for string labels. */
|
|
19
|
+
removeLabel?: string;
|
|
20
|
+
/** 'md' = 28px (default) - 'sm' = 24px for dense rows. */
|
|
21
|
+
size?: 'md' | 'sm';
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** TagGroup - wrapping flex row + removal/insertion choreography. */
|
|
27
|
+
export interface TagGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
28
|
+
/** Accessible name for the group (role="group"). */
|
|
29
|
+
label?: string;
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const TagGroupContext = React.createContext(false);
|
|
35
|
+
|
|
36
|
+
/* GOTCHA (buildless pages): split props manually, not object-rest. */
|
|
37
|
+
const TAG_OWN_PROPS: Record<string, number> = {
|
|
38
|
+
children: 1,
|
|
39
|
+
icon: 1,
|
|
40
|
+
onRemove: 1,
|
|
41
|
+
removeLabel: 1,
|
|
42
|
+
size: 1,
|
|
43
|
+
disabled: 1,
|
|
44
|
+
className: 1,
|
|
45
|
+
};
|
|
46
|
+
function tagRestProps(props: Record<string, unknown>, own: Record<string, number>) {
|
|
47
|
+
const rest: Record<string, unknown> = {};
|
|
48
|
+
for (const k in props) {
|
|
49
|
+
if (!own[k]) rest[k] = props[k];
|
|
50
|
+
}
|
|
51
|
+
return rest;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* The remove glyph - drawn inline (path-level, per section D) so it can wind up on hover. */
|
|
55
|
+
function TagRemoveGlyph() {
|
|
56
|
+
return (
|
|
57
|
+
<svg viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
58
|
+
<path d="M3 3 L9 9 M9 3 L3 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
59
|
+
</svg>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function Tag(props: TagProps) {
|
|
64
|
+
const {
|
|
65
|
+
children,
|
|
66
|
+
icon = null,
|
|
67
|
+
onRemove = null,
|
|
68
|
+
removeLabel,
|
|
69
|
+
size = 'md',
|
|
70
|
+
disabled = false,
|
|
71
|
+
className = '',
|
|
72
|
+
} = props;
|
|
73
|
+
const rest = tagRestProps(props as unknown as Record<string, unknown>, TAG_OWN_PROPS);
|
|
74
|
+
const grouped = React.useContext(TagGroupContext);
|
|
75
|
+
const classes = ['tag', size === 'sm' ? 'tag--sm' : '', className].filter(Boolean).join(' ');
|
|
76
|
+
|
|
77
|
+
const xLabel = removeLabel || (typeof children === 'string' ? 'Remove ' + children : 'Remove');
|
|
78
|
+
|
|
79
|
+
const content = (
|
|
80
|
+
<React.Fragment>
|
|
81
|
+
{icon && (
|
|
82
|
+
<span className="tag__icon">
|
|
83
|
+
<IconSlot size="sm">{icon}</IconSlot>
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
86
|
+
<span className="tag__label">{children}</span>
|
|
87
|
+
{onRemove && (
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
className="tag__remove"
|
|
91
|
+
aria-label={xLabel}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
onClick={onRemove}
|
|
94
|
+
>
|
|
95
|
+
<TagRemoveGlyph />
|
|
96
|
+
</button>
|
|
97
|
+
)}
|
|
98
|
+
</React.Fragment>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (!grouped) {
|
|
102
|
+
return (
|
|
103
|
+
<span className={classes} data-disabled={disabled ? 'true' : undefined} {...rest}>
|
|
104
|
+
{content}
|
|
105
|
+
</span>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { t } = UIMotion;
|
|
110
|
+
return (
|
|
111
|
+
<motion.span
|
|
112
|
+
layout
|
|
113
|
+
className={classes}
|
|
114
|
+
data-disabled={disabled ? 'true' : undefined}
|
|
115
|
+
initial={{ opacity: 0, scale: 0.9 }}
|
|
116
|
+
animate={{ opacity: 1, scale: 1, transition: t.enter }}
|
|
117
|
+
exit={{ opacity: 0, scale: 0.9, transition: t.exit }}
|
|
118
|
+
transition={t.layout}
|
|
119
|
+
{...rest}
|
|
120
|
+
>
|
|
121
|
+
{content}
|
|
122
|
+
</motion.span>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function TagGroup(props: TagGroupProps) {
|
|
127
|
+
const { label, className = '', children } = props;
|
|
128
|
+
const rest = tagRestProps(props as unknown as Record<string, unknown>, {
|
|
129
|
+
label: 1,
|
|
130
|
+
className: 1,
|
|
131
|
+
children: 1,
|
|
132
|
+
});
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
className={['tag-group', className].filter(Boolean).join(' ')}
|
|
136
|
+
role="group"
|
|
137
|
+
aria-label={label}
|
|
138
|
+
{...rest}
|
|
139
|
+
>
|
|
140
|
+
<TagGroupContext.Provider value={true}>
|
|
141
|
+
<AnimatePresence initial={false}>{children}</AnimatePresence>
|
|
142
|
+
</TagGroupContext.Provider>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/* ToggleTag - on/off filter chip (<button aria-pressed>); owns its selection state. */
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { IconSlot } from '../icon/IconSlot';
|
|
7
|
+
|
|
8
|
+
/** ToggleTag - the on/off filter chip (<button aria-pressed>). */
|
|
9
|
+
export interface ToggleTagProps extends Omit<
|
|
10
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
11
|
+
'onChange' | 'children'
|
|
12
|
+
> {
|
|
13
|
+
/** The label. */
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
/** Controlled selected value. Omit for uncontrolled. */
|
|
16
|
+
selected?: boolean;
|
|
17
|
+
/** Uncontrolled initial value. */
|
|
18
|
+
defaultSelected?: boolean;
|
|
19
|
+
/** Fires with the next value on every toggle. */
|
|
20
|
+
onChange?: (selected: boolean) => void;
|
|
21
|
+
/** Your own icon node; replaces the tick. Selected state reads from the wash. */
|
|
22
|
+
icon?: React.ReactNode | null;
|
|
23
|
+
/** Optional result count - rendered mono/tabular. */
|
|
24
|
+
count?: React.ReactNode;
|
|
25
|
+
/** 'md' = 28px (default) - 'sm' = 24px for dense rows. */
|
|
26
|
+
size?: 'md' | 'sm';
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* GOTCHA: split props manually instead of object-rest. */
|
|
32
|
+
const TOGGLE_TAG_OWN_PROPS: Record<string, number> = {
|
|
33
|
+
children: 1,
|
|
34
|
+
selected: 1,
|
|
35
|
+
defaultSelected: 1,
|
|
36
|
+
onChange: 1,
|
|
37
|
+
icon: 1,
|
|
38
|
+
count: 1,
|
|
39
|
+
size: 1,
|
|
40
|
+
disabled: 1,
|
|
41
|
+
className: 1,
|
|
42
|
+
};
|
|
43
|
+
function toggleTagRestProps(props: Record<string, unknown>, own: Record<string, number>) {
|
|
44
|
+
const rest: Record<string, unknown> = {};
|
|
45
|
+
for (const k in props) {
|
|
46
|
+
if (!own[k]) rest[k] = props[k];
|
|
47
|
+
}
|
|
48
|
+
return rest;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ToggleTagTick({ selected }: { selected: boolean }) {
|
|
52
|
+
/* collapse.css mechanism on spans - width 0fr and 1fr, content clipped */
|
|
53
|
+
return (
|
|
54
|
+
<span
|
|
55
|
+
className="tag__check collapse"
|
|
56
|
+
data-axis="width"
|
|
57
|
+
data-open={selected ? 'true' : 'false'}
|
|
58
|
+
aria-hidden="true"
|
|
59
|
+
>
|
|
60
|
+
<span className="collapse__inner">
|
|
61
|
+
<svg viewBox="0 0 12 12" fill="none">
|
|
62
|
+
<path
|
|
63
|
+
d="M2.5 6.5 L5 9 L9.5 3.5"
|
|
64
|
+
pathLength="1"
|
|
65
|
+
strokeWidth="1.5"
|
|
66
|
+
strokeLinecap="round"
|
|
67
|
+
strokeLinejoin="round"
|
|
68
|
+
/>
|
|
69
|
+
</svg>
|
|
70
|
+
</span>
|
|
71
|
+
</span>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function ToggleTag(props: ToggleTagProps) {
|
|
76
|
+
const {
|
|
77
|
+
children,
|
|
78
|
+
selected: controlledSelected,
|
|
79
|
+
defaultSelected = false,
|
|
80
|
+
onChange,
|
|
81
|
+
icon = null,
|
|
82
|
+
count = null,
|
|
83
|
+
size = 'md',
|
|
84
|
+
disabled = false,
|
|
85
|
+
className = '',
|
|
86
|
+
} = props;
|
|
87
|
+
const rest = toggleTagRestProps(
|
|
88
|
+
props as unknown as Record<string, unknown>,
|
|
89
|
+
TOGGLE_TAG_OWN_PROPS,
|
|
90
|
+
);
|
|
91
|
+
const [uncontrolled, setUncontrolled] = React.useState(defaultSelected);
|
|
92
|
+
const isControlled = controlledSelected !== undefined;
|
|
93
|
+
const selected = isControlled ? controlledSelected : uncontrolled;
|
|
94
|
+
|
|
95
|
+
function toggle() {
|
|
96
|
+
const next = !selected;
|
|
97
|
+
if (!isControlled) setUncontrolled(next);
|
|
98
|
+
if (onChange) onChange(next);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const classes = ['tag', 'tag--toggle', size === 'sm' ? 'tag--sm' : '', className]
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.join(' ');
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
className={classes}
|
|
109
|
+
aria-pressed={selected}
|
|
110
|
+
disabled={disabled}
|
|
111
|
+
onClick={toggle}
|
|
112
|
+
{...rest}
|
|
113
|
+
>
|
|
114
|
+
{icon ? (
|
|
115
|
+
<span className="tag__icon">
|
|
116
|
+
<IconSlot size="sm">{icon}</IconSlot>
|
|
117
|
+
</span>
|
|
118
|
+
) : (
|
|
119
|
+
<ToggleTagTick selected={selected} />
|
|
120
|
+
)}
|
|
121
|
+
<span className="tag__label">{children}</span>
|
|
122
|
+
{count != null && <span className="tag__count">{count}</span>}
|
|
123
|
+
</button>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/* Tag / chip - interactive label (button-family chrome): base .tag + .tag--toggle sibling. */
|
|
2
|
+
|
|
3
|
+
.tag {
|
|
4
|
+
--tag-h: var(--control-height-sm);
|
|
5
|
+
/* button-family DNA: lit from above + a contact shadow (.btn--secondary) */
|
|
6
|
+
--tag-shadow: inset 0 1px 0 0 rgb(255 255 255 / 0.6), 0 1px 2px 0 rgb(var(--shadow-rgb) / 0.05);
|
|
7
|
+
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: var(--space-1);
|
|
11
|
+
height: var(--tag-h);
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
padding-inline: var(--space-2);
|
|
14
|
+
border: var(--border-hairline) solid var(--border-default);
|
|
15
|
+
border-radius: var(--radius-sm);
|
|
16
|
+
background: var(--bg-surface);
|
|
17
|
+
color: var(--text-body);
|
|
18
|
+
|
|
19
|
+
font-family: var(--font-sans);
|
|
20
|
+
font-size: var(--size-caption);
|
|
21
|
+
line-height: 1;
|
|
22
|
+
font-weight: var(--weight-medium);
|
|
23
|
+
letter-spacing: var(--tracking-normal);
|
|
24
|
+
white-space: nowrap;
|
|
25
|
+
vertical-align: middle;
|
|
26
|
+
user-select: none;
|
|
27
|
+
-webkit-user-select: none;
|
|
28
|
+
box-shadow: var(--tag-shadow);
|
|
29
|
+
|
|
30
|
+
/* no transform transition here - Motion writes transforms on grouped tags (section C) */
|
|
31
|
+
transition: var(--transition-control);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.tag__label {
|
|
35
|
+
min-width: 0;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
text-overflow: ellipsis;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.tag--sm {
|
|
41
|
+
--tag-h: var(--space-5);
|
|
42
|
+
font-size: var(--size-micro);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tag__icon {
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
flex: none;
|
|
48
|
+
}
|
|
49
|
+
.tag__icon svg,
|
|
50
|
+
.tag__icon i {
|
|
51
|
+
display: block;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Dual-weight icon crossfade - transitions, not animations, so nothing plays on first paint. */
|
|
55
|
+
.tag__icon--dual {
|
|
56
|
+
display: inline-grid;
|
|
57
|
+
}
|
|
58
|
+
.tag__icon--dual > :is(i, svg) {
|
|
59
|
+
grid-area: 1 / 1;
|
|
60
|
+
transition:
|
|
61
|
+
opacity var(--duration-base) var(--ease-standard),
|
|
62
|
+
transform var(--duration-base) var(--ease-standard);
|
|
63
|
+
}
|
|
64
|
+
.tag__icon--dual > :is(i, svg):last-child {
|
|
65
|
+
opacity: 0;
|
|
66
|
+
transform: scale(0.6);
|
|
67
|
+
}
|
|
68
|
+
.tag--toggle[aria-pressed='true'] .tag__icon--dual > :is(i, svg):first-child {
|
|
69
|
+
opacity: 0;
|
|
70
|
+
}
|
|
71
|
+
.tag--toggle[aria-pressed='true'] .tag__icon--dual > :is(i, svg):last-child {
|
|
72
|
+
opacity: 1;
|
|
73
|
+
transform: scale(1);
|
|
74
|
+
transition:
|
|
75
|
+
opacity var(--duration-fast) var(--ease-standard),
|
|
76
|
+
transform var(--duration-slow) var(--ease-spring);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.tag__count {
|
|
80
|
+
font-family: var(--font-mono);
|
|
81
|
+
font-size: var(--size-micro);
|
|
82
|
+
font-variant-numeric: tabular-nums;
|
|
83
|
+
color: var(--text-subtle);
|
|
84
|
+
transition: color var(--duration-fast) var(--ease-standard);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.tag__remove {
|
|
88
|
+
display: inline-flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
flex: none;
|
|
92
|
+
width: calc(var(--tag-h) - var(--space-2));
|
|
93
|
+
height: calc(var(--tag-h) - var(--space-2));
|
|
94
|
+
margin-right: calc(var(--space-1) * -1);
|
|
95
|
+
padding: 0;
|
|
96
|
+
border: 0;
|
|
97
|
+
border-radius: var(--radius-full);
|
|
98
|
+
background: transparent;
|
|
99
|
+
color: var(--text-subtle);
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
appearance: none;
|
|
102
|
+
-webkit-tap-highlight-color: transparent;
|
|
103
|
+
transition:
|
|
104
|
+
color var(--duration-fast) var(--ease-standard),
|
|
105
|
+
background-color var(--duration-fast) var(--ease-standard),
|
|
106
|
+
transform var(--duration-fast) var(--ease-standard);
|
|
107
|
+
}
|
|
108
|
+
.tag__remove svg {
|
|
109
|
+
display: block;
|
|
110
|
+
width: var(--space-3);
|
|
111
|
+
height: var(--space-3);
|
|
112
|
+
transition: transform var(--duration-base) var(--ease-standard);
|
|
113
|
+
}
|
|
114
|
+
.tag__remove:hover {
|
|
115
|
+
color: var(--text-strong);
|
|
116
|
+
background: var(--bg-muted);
|
|
117
|
+
}
|
|
118
|
+
.tag__remove:hover svg {
|
|
119
|
+
transform: rotate(30deg);
|
|
120
|
+
}
|
|
121
|
+
.tag__remove:active {
|
|
122
|
+
transform: scale(0.9);
|
|
123
|
+
}
|
|
124
|
+
.tag__remove:focus-visible {
|
|
125
|
+
outline: none;
|
|
126
|
+
box-shadow: var(--focus-ring);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.tag:has(.tag__remove:hover):not([data-disabled]),
|
|
130
|
+
.tag:has(.tag__remove:focus-visible) {
|
|
131
|
+
color: var(--text-muted);
|
|
132
|
+
border-color: var(--border-strong);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.tag--toggle {
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
appearance: none;
|
|
138
|
+
-webkit-tap-highlight-color: transparent;
|
|
139
|
+
transition:
|
|
140
|
+
var(--transition-control),
|
|
141
|
+
transform var(--duration-fast) var(--ease-standard);
|
|
142
|
+
}
|
|
143
|
+
.tag--toggle:hover:not(:disabled) {
|
|
144
|
+
color: var(--text-strong);
|
|
145
|
+
border-color: var(--border-strong);
|
|
146
|
+
background: var(--bg-subtle);
|
|
147
|
+
}
|
|
148
|
+
.tag--toggle:active:not(:disabled) {
|
|
149
|
+
transform: translateY(var(--space-px));
|
|
150
|
+
background: var(--bg-muted);
|
|
151
|
+
--tag-shadow: inset 0 1px 2px 0 rgb(var(--shadow-rgb) / 0.07);
|
|
152
|
+
}
|
|
153
|
+
/* composed ring - never erases the resting shadow (.btn pattern) */
|
|
154
|
+
.tag--toggle:focus-visible {
|
|
155
|
+
outline: none;
|
|
156
|
+
box-shadow: var(--tag-shadow), var(--focus-ring);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Selected = accent wash, never a solid fill; the top sheen comes off (matte), the contact shadow stays. */
|
|
160
|
+
.tag--toggle[aria-pressed='true'] {
|
|
161
|
+
color: var(--text-accent);
|
|
162
|
+
border-color: var(--accent-border);
|
|
163
|
+
background: var(--accent-wash);
|
|
164
|
+
--tag-shadow: 0 1px 2px 0 rgb(var(--shadow-rgb) / 0.05);
|
|
165
|
+
}
|
|
166
|
+
.tag--toggle[aria-pressed='true']:hover:not(:disabled) {
|
|
167
|
+
color: var(--accent-active);
|
|
168
|
+
border-color: var(--accent-border);
|
|
169
|
+
background: var(--accent-wash);
|
|
170
|
+
}
|
|
171
|
+
.tag--toggle[aria-pressed='true']:active:not(:disabled) {
|
|
172
|
+
background: var(--accent-subtle);
|
|
173
|
+
}
|
|
174
|
+
.tag--toggle[aria-pressed='true'] .tag__count {
|
|
175
|
+
color: inherit;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Tick slot (Collapse width mechanism); the negative margin cancels the flex gap while collapsed, the inner margin restores it when open. */
|
|
179
|
+
.tag__check {
|
|
180
|
+
margin-right: calc(var(--space-1) * -1);
|
|
181
|
+
}
|
|
182
|
+
.tag__check svg {
|
|
183
|
+
display: block;
|
|
184
|
+
width: var(--space-3);
|
|
185
|
+
height: var(--space-3);
|
|
186
|
+
margin-right: var(--space-1);
|
|
187
|
+
}
|
|
188
|
+
.tag__check path {
|
|
189
|
+
stroke: currentColor;
|
|
190
|
+
stroke-dasharray: 1;
|
|
191
|
+
stroke-dashoffset: 1;
|
|
192
|
+
transition: stroke-dashoffset var(--duration-base) var(--ease-standard);
|
|
193
|
+
}
|
|
194
|
+
.tag--toggle[aria-pressed='true'] .tag__check path {
|
|
195
|
+
stroke-dashoffset: 0;
|
|
196
|
+
transition: stroke-dashoffset var(--duration-slow) var(--ease-entrance) 60ms;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.tag[data-disabled],
|
|
200
|
+
.tag--toggle:disabled {
|
|
201
|
+
color: var(--text-disabled);
|
|
202
|
+
border-color: var(--border-subtle);
|
|
203
|
+
background: var(--bg-surface);
|
|
204
|
+
cursor: default;
|
|
205
|
+
--tag-shadow: none;
|
|
206
|
+
}
|
|
207
|
+
.tag[data-disabled] .tag__count,
|
|
208
|
+
.tag--toggle:disabled .tag__count {
|
|
209
|
+
color: var(--text-disabled);
|
|
210
|
+
}
|
|
211
|
+
.tag[data-disabled] .tag__remove {
|
|
212
|
+
color: var(--text-disabled);
|
|
213
|
+
background: transparent;
|
|
214
|
+
cursor: default;
|
|
215
|
+
pointer-events: none;
|
|
216
|
+
}
|
|
217
|
+
.tag--toggle:disabled {
|
|
218
|
+
transform: none;
|
|
219
|
+
}
|
|
220
|
+
.tag--toggle:disabled[aria-pressed='true'] {
|
|
221
|
+
background: var(--bg-muted);
|
|
222
|
+
border-color: var(--border-subtle);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.tag-group {
|
|
226
|
+
display: flex;
|
|
227
|
+
flex-wrap: wrap;
|
|
228
|
+
align-items: center;
|
|
229
|
+
gap: var(--space-2);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@media (prefers-reduced-motion: reduce) {
|
|
233
|
+
.tag__remove svg {
|
|
234
|
+
transition: none;
|
|
235
|
+
}
|
|
236
|
+
.tag__remove:hover svg {
|
|
237
|
+
transform: none;
|
|
238
|
+
}
|
|
239
|
+
.tag__remove:active {
|
|
240
|
+
transform: none;
|
|
241
|
+
}
|
|
242
|
+
.tag--toggle:active:not(:disabled) {
|
|
243
|
+
transform: none;
|
|
244
|
+
}
|
|
245
|
+
.tag__icon--dual > :is(i, svg) {
|
|
246
|
+
transition: none;
|
|
247
|
+
}
|
|
248
|
+
}
|