@ponti-studios/ui 0.1.2 → 0.4.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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -7
  3. package/THIRD_PARTY_NOTICES.md +17 -0
  4. package/dist/index.js +988 -640
  5. package/dist/index.js.map +1 -1
  6. package/package.json +41 -41
  7. package/src/animations.css +42 -383
  8. package/src/components/data-display/accordion.tsx +22 -10
  9. package/src/components/data-display/filter-chip.stories.tsx +38 -0
  10. package/src/components/data-display/filter-chip.tsx +10 -4
  11. package/src/components/data-display/index.ts +5 -0
  12. package/src/components/data-display/metric-card.stories.tsx +48 -0
  13. package/src/components/data-display/metric-card.tsx +20 -0
  14. package/src/components/data-display/sort-controls.stories.tsx +45 -0
  15. package/src/components/data-display/sort-controls.tsx +99 -0
  16. package/src/components/data-display/sort-row.tsx +74 -0
  17. package/src/components/data-display/table.tsx +5 -9
  18. package/src/components/feedback/alert.tsx +4 -4
  19. package/src/components/feedback/empty-state.tsx +16 -6
  20. package/src/components/feedback/file-upload-status-badge.stories.tsx +28 -0
  21. package/src/components/feedback/file-upload-status-badge.tsx +33 -0
  22. package/src/components/feedback/file-upload-status.stories.tsx +57 -0
  23. package/src/components/feedback/file-upload-status.tsx +152 -0
  24. package/src/components/feedback/index.ts +9 -0
  25. package/src/components/feedback/progress-bar.stories.tsx +39 -0
  26. package/src/components/feedback/progress-bar.tsx +21 -0
  27. package/src/components/feedback/progress.stories.tsx +8 -6
  28. package/src/components/feedback/progress.tsx +3 -3
  29. package/src/components/feedback/skeleton.stories.tsx +36 -0
  30. package/src/components/feedback/skeleton.tsx +7 -0
  31. package/src/components/feedback/spinner.tsx +2 -2
  32. package/src/components/feedback/update-guard.stories.tsx +32 -0
  33. package/src/components/feedback/update-guard.tsx +280 -0
  34. package/src/components/forms/calendar.tsx +14 -11
  35. package/src/components/forms/date-month-select.stories.tsx +26 -0
  36. package/src/components/forms/date-month-select.tsx +70 -0
  37. package/src/components/forms/date-picker.tsx +3 -3
  38. package/src/components/forms/drop-zone.stories.tsx +56 -0
  39. package/src/components/forms/drop-zone.tsx +146 -0
  40. package/src/components/forms/entity-select.stories.tsx +65 -0
  41. package/src/components/forms/entity-select.tsx +79 -0
  42. package/src/components/forms/field.tsx +3 -3
  43. package/src/components/forms/group-by-select.stories.tsx +26 -0
  44. package/src/components/forms/group-by-select.tsx +47 -0
  45. package/src/components/forms/index.ts +10 -0
  46. package/src/components/forms/input.stories.tsx +4 -4
  47. package/src/components/forms/input.tsx +1 -1
  48. package/src/components/forms/passkey-management.stories.tsx +81 -0
  49. package/src/components/forms/passkey-management.tsx +138 -0
  50. package/src/components/forms/radio-group.tsx +14 -13
  51. package/src/components/forms/select.stories.tsx +4 -4
  52. package/src/components/forms/select.tsx +47 -48
  53. package/src/components/forms/slider.stories.tsx +5 -2
  54. package/src/components/forms/slider.tsx +39 -17
  55. package/src/components/forms/stepper.tsx +4 -4
  56. package/src/components/forms/switch.stories.tsx +1 -1
  57. package/src/components/forms/switch.tsx +2 -2
  58. package/src/components/forms/textarea.stories.tsx +2 -2
  59. package/src/components/forms/textarea.tsx +1 -1
  60. package/src/components/layout/index.ts +3 -0
  61. package/src/components/layout/scroll-area.stories.tsx +4 -4
  62. package/src/components/layout/scroll-area.tsx +23 -21
  63. package/src/components/layout/section-intro.stories.tsx +29 -0
  64. package/src/components/layout/section-intro.tsx +31 -0
  65. package/src/components/layout/surface-panel.stories.tsx +32 -0
  66. package/src/components/layout/surface-panel.tsx +15 -0
  67. package/src/components/navigation/app-navigation.stories.tsx +2 -2
  68. package/src/components/navigation/app-navigation.tsx +13 -13
  69. package/src/components/navigation/index.ts +4 -0
  70. package/src/components/navigation/pagination-controls.stories.tsx +41 -0
  71. package/src/components/navigation/pagination-controls.tsx +50 -0
  72. package/src/components/navigation/route-link.stories.tsx +30 -0
  73. package/src/components/navigation/route-link.tsx +42 -0
  74. package/src/components/navigation/tabs.stories.tsx +5 -5
  75. package/src/components/navigation/tabs.tsx +8 -8
  76. package/src/components/overlays/alert-dialog.tsx +22 -25
  77. package/src/components/overlays/command.tsx +98 -26
  78. package/src/components/overlays/dialog.tsx +5 -5
  79. package/src/components/overlays/dropdown-menu.stories.tsx +0 -5
  80. package/src/components/overlays/dropdown-menu.tsx +49 -31
  81. package/src/components/overlays/popover.stories.tsx +2 -2
  82. package/src/components/overlays/popover.tsx +43 -14
  83. package/src/components/overlays/sheet.stories.tsx +2 -2
  84. package/src/components/overlays/sheet.tsx +51 -17
  85. package/src/components/primitives/avatar.tsx +9 -5
  86. package/src/components/primitives/badge.tsx +11 -12
  87. package/src/components/primitives/button.tsx +49 -23
  88. package/src/components/primitives/card.stories.tsx +2 -2
  89. package/src/components/primitives/card.tsx +2 -5
  90. package/src/components/primitives/index.ts +2 -0
  91. package/src/components/primitives/label.stories.tsx +1 -1
  92. package/src/components/primitives/label.tsx +5 -6
  93. package/src/components/primitives/status-badge.stories.tsx +52 -0
  94. package/src/components/primitives/status-badge.tsx +33 -0
  95. package/src/constants/chart-colors.ts +7 -7
  96. package/src/hooks/index.ts +1 -0
  97. package/src/hooks/use-sort.ts +20 -0
  98. package/src/index.ts +30 -11
  99. package/src/styles.css +100 -974
  100. package/src/tokens/chart-colors.stories.tsx +192 -0
  101. package/src/tokens/color-systems.stories.tsx +20 -112
  102. package/src/tokens/foundations.stories.tsx +58 -100
  103. package/src/tokens/generated/dark.colors.generated.ts +29 -0
  104. package/src/tokens/generated/dark.css +29 -0
  105. package/src/tokens/generated/foundations.css +148 -0
  106. package/src/tokens/generated/foundations.generated.ts +451 -0
  107. package/src/tokens/generated/light.colors.generated.ts +29 -0
  108. package/src/tokens/generated/light.css +29 -0
  109. package/src/tokens/index.ts +79 -16
  110. package/src/tokens/layout.stories.tsx +23 -91
  111. package/src/tokens/colors.ts +0 -156
  112. package/src/tokens/motion.ts +0 -51
  113. package/src/tokens/radii.ts +0 -10
  114. package/src/tokens/shadows.ts +0 -34
  115. package/src/tokens/spacing.ts +0 -30
  116. package/src/tokens/typography.ts +0 -42
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@ponti-studios/ui",
3
- "version": "0.1.2",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "Shared UI component library for ponti-studios labs",
6
- "license": "UNLICENSED",
6
+ "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/ponti-studios/labs.git",
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "files": [
17
17
  "dist",
18
- "src"
18
+ "src",
19
+ "THIRD_PARTY_NOTICES.md"
19
20
  ],
20
21
  "type": "module",
21
22
  "sideEffects": [
@@ -70,61 +71,60 @@
70
71
  "./styles.css": "./src/styles.css",
71
72
  "./animations.css": "./src/animations.css"
72
73
  },
74
+ "scripts": {
75
+ "build": "vite build",
76
+ "dev": "vite",
77
+ "storybook": "style-dictionary build --config style-dictionary.dark-storybook.json && storybook dev -p 6006",
78
+ "check:source": "node tokens/check-source.mjs",
79
+ "test:a11y": "vitest --run --config vitest.config.ts",
80
+ "tokens:build": "style-dictionary build --config style-dictionary.foundations.json && style-dictionary build --config style-dictionary.light.json && style-dictionary build --config style-dictionary.dark.json && style-dictionary build --config style-dictionary.dark-storybook.json",
81
+ "tokens:check": "pnpm run tokens:build && node tokens/check-foundations.mjs && git diff --exit-code -- src/tokens/generated",
82
+ "typecheck": "tsc --noEmit",
83
+ "lint": "oxlint src --fix",
84
+ "format": "oxfmt src --write",
85
+ "prepack": "pnpm run tokens:check && pnpm run check:source && pnpm run typecheck && pnpm run build"
86
+ },
73
87
  "dependencies": {
74
88
  "@base-ui/react": "^1.6.0",
75
- "@radix-ui/react-accordion": "^1.2.14",
76
- "@radix-ui/react-alert-dialog": "^1.1.17",
77
- "@radix-ui/react-avatar": "^1.2.2",
78
- "@radix-ui/react-dialog": "^1.1.17",
79
- "@radix-ui/react-dropdown-menu": "^2.1.16",
80
- "@radix-ui/react-label": "^2.1.10",
81
- "@radix-ui/react-popover": "^1.1.19",
82
- "@radix-ui/react-progress": "^1.1.10",
83
- "@radix-ui/react-radio-group": "^1.4.1",
84
- "@radix-ui/react-select": "^2.3.1",
85
- "@radix-ui/react-slider": "^1.4.1",
86
- "@radix-ui/react-slot": "^1.3.0",
87
- "@radix-ui/react-switch": "^1.2.6",
88
- "@radix-ui/react-tabs": "^1.1.15",
89
+ "@radix-ui/colors": "^3.0.0",
89
90
  "class-variance-authority": "^0.7.1",
90
91
  "clsx": "^2.1.1",
91
- "cmdk": "^1.1.1",
92
92
  "date-fns": "^4.1.0",
93
- "framer-motion": "^12.42.0",
94
- "lucide-react": "^1.21.0",
95
- "react-day-picker": "^9.11.1",
93
+ "lucide-react": "^1.24.0",
94
+ "react-day-picker": "^10.0.1",
96
95
  "tailwind-merge": "^3.6.0"
97
96
  },
98
97
  "devDependencies": {
99
98
  "@chromatic-com/storybook": "^5.2.1",
100
- "@storybook/addon-docs": "^10.4.6",
101
- "@storybook/addon-links": "^10.4.6",
102
- "@storybook/addon-onboarding": "^10.4.6",
103
- "@storybook/react-vite": "^10.4.6",
104
- "@tailwindcss/vite": "^4.3.1",
105
- "@types/node": "^26.0.1",
99
+ "@storybook/addon-a11y": "10.5.3",
100
+ "@storybook/addon-docs": "^10.5.3",
101
+ "@storybook/addon-links": "^10.5.3",
102
+ "@storybook/addon-onboarding": "^10.5.3",
103
+ "@storybook/addon-themes": "10.5.3",
104
+ "@storybook/addon-vitest": "10.5.3",
105
+ "@storybook/react-vite": "^10.5.3",
106
+ "@tailwindcss/vite": "catalog:",
107
+ "@types/node": "catalog:",
106
108
  "@types/react": "^19.2.17",
107
109
  "@types/react-dom": "^19.2.3",
108
110
  "@vitejs/plugin-react": "^6.0.3",
109
- "oxfmt": "0.56.0",
110
- "oxlint": "^1.71.0",
111
+ "@vitest/browser": "4.1.10",
112
+ "@vitest/browser-playwright": "4.1.10",
113
+ "oxfmt": "catalog:shared",
114
+ "oxlint": "catalog:shared",
115
+ "playwright": "catalog:",
111
116
  "react": "^19.2.7",
112
117
  "react-dom": "^19.2.7",
113
- "storybook": "^10.4.6",
114
- "tailwindcss": "^4.3.1",
118
+ "storybook": "^10.5.3",
119
+ "style-dictionary": "5.5.0",
120
+ "tailwindcss": "catalog:",
115
121
  "typescript": "6.0.3",
116
- "vite": "^8.1.0"
122
+ "vite": "catalog:",
123
+ "vitest": "catalog:"
117
124
  },
118
125
  "peerDependencies": {
119
126
  "react": "^18.2.0 || ^19.0.0",
120
127
  "react-dom": "^18.2.0 || ^19.0.0"
121
128
  },
122
- "scripts": {
123
- "build": "vite build",
124
- "dev": "vite",
125
- "storybook": "storybook dev -p 6006",
126
- "typecheck": "tsc --noEmit",
127
- "lint": "oxlint src --fix",
128
- "format": "oxfmt src --write"
129
- }
130
- }
129
+ "packageManager": "pnpm@10.30.3"
130
+ }
@@ -1,437 +1,96 @@
1
- @media (prefers-reduced-motion: reduce) {
2
- .motion-anim-enter,
3
- .motion-anim-exit,
4
- .motion-anim-enter-top,
5
- .motion-anim-enter-bottom,
6
- .motion-anim-enter-left,
7
- .motion-anim-enter-right,
8
- .motion-anim-breezy,
9
- .motion-anim-breezy-loop,
10
- .motion-anim-breezy-progress,
11
- .motion-anim-breezy-stagger > * {
12
- animation: none !important;
13
- opacity: 1 !important;
14
- transform: none !important;
15
- }
16
- }
17
-
18
- @keyframes motion-enter {
19
- from {
20
- opacity: 0;
21
- transform: translateY(6px);
22
- }
23
- to {
24
- opacity: 1;
25
- transform: translateY(0);
26
- }
27
- }
28
-
29
- @keyframes motion-exit {
30
- from {
31
- opacity: 1;
32
- transform: translateY(0);
33
- }
34
- to {
35
- opacity: 0;
36
- transform: translateY(4px);
37
- }
38
- }
39
-
40
- @keyframes motion-enter-top {
41
- from {
42
- opacity: 0;
43
- transform: translateY(-6px);
44
- }
45
- to {
46
- opacity: 1;
47
- transform: translateY(0);
48
- }
49
- }
50
-
51
- @keyframes motion-exit-top {
52
- from {
53
- opacity: 1;
54
- transform: translateY(0);
55
- }
56
- to {
57
- opacity: 0;
58
- transform: translateY(-4px);
59
- }
60
- }
61
-
62
- @keyframes motion-enter-bottom {
63
- from {
64
- opacity: 0;
65
- transform: translateY(6px);
66
- }
67
- to {
68
- opacity: 1;
69
- transform: translateY(0);
70
- }
71
- }
72
-
73
- @keyframes motion-exit-bottom {
74
- from {
75
- opacity: 1;
76
- transform: translateY(0);
77
- }
78
- to {
79
- opacity: 0;
80
- transform: translateY(6px);
81
- }
82
- }
83
-
84
- @keyframes motion-enter-left {
1
+ @keyframes accordion-down {
85
2
  from {
86
- opacity: 0;
87
- transform: translateX(-6px);
3
+ height: 0;
88
4
  }
89
5
  to {
90
- opacity: 1;
91
- transform: translateX(0);
6
+ height: var(--accordion-content-height);
92
7
  }
93
8
  }
94
-
95
- @keyframes motion-exit-left {
9
+ @keyframes accordion-up {
96
10
  from {
97
- opacity: 1;
98
- transform: translateX(0);
11
+ height: var(--accordion-content-height);
99
12
  }
100
13
  to {
101
- opacity: 0;
102
- transform: translateX(-4px);
14
+ height: 0;
103
15
  }
104
16
  }
105
-
106
- @keyframes motion-enter-right {
17
+ @keyframes fade-in {
107
18
  from {
108
19
  opacity: 0;
109
- transform: translateX(6px);
110
20
  }
111
21
  to {
112
22
  opacity: 1;
113
- transform: translateX(0);
114
23
  }
115
24
  }
116
-
117
- @keyframes motion-exit-right {
25
+ @keyframes fade-out {
118
26
  from {
119
27
  opacity: 1;
120
- transform: translateX(0);
121
28
  }
122
29
  to {
123
30
  opacity: 0;
124
- transform: translateX(4px);
125
- }
126
- }
127
-
128
- /*
129
- * Sheet/drawer slide — full off-canvas travel (not the 4-6px "nudge" used by
130
- * the motion-enter/exit-* keyframes above, which are meant for small popovers).
131
- * Gated on the real [data-state] attribute so open/closed never race — see
132
- * the note above the data-state fade/zoom rules for why that matters here.
133
- */
134
- @keyframes sheet-slide-in-from-right {
135
- from {
136
- transform: translateX(100%);
137
- }
138
- to {
139
- transform: translateX(0);
140
- }
141
- }
142
- @keyframes sheet-slide-out-to-right {
143
- from {
144
- transform: translateX(0);
145
- }
146
- to {
147
- transform: translateX(100%);
148
- }
149
- }
150
- @keyframes sheet-slide-in-from-bottom {
151
- from {
152
- transform: translateY(100%);
153
- }
154
- to {
155
- transform: translateY(0);
156
- }
157
- }
158
- @keyframes sheet-slide-out-to-bottom {
159
- from {
160
- transform: translateY(0);
161
- }
162
- to {
163
- transform: translateY(100%);
164
- }
165
- }
166
-
167
- .sheet-responsive[data-state="open"] {
168
- animation: sheet-slide-in-from-bottom 480ms cubic-bezier(0.16, 1, 0.3, 1) both;
169
- }
170
- .sheet-responsive[data-state="closed"] {
171
- animation: sheet-slide-out-to-bottom 280ms cubic-bezier(0.4, 0, 1, 1) both;
172
- }
173
-
174
- @media (min-width: 768px) {
175
- .sheet-responsive[data-state="open"] {
176
- animation-name: sheet-slide-in-from-right;
177
- }
178
-
179
- .sheet-responsive[data-state="closed"] {
180
- animation-name: sheet-slide-out-to-right;
181
- }
182
- }
183
-
184
- @media (prefers-reduced-motion: reduce) {
185
- .sheet-responsive {
186
- animation: none !important;
187
- transform: none !important;
188
- }
189
- }
190
-
191
- @keyframes motion-breezy-wave {
192
- 0% {
193
- transform: translateY(0);
194
- opacity: 0.9;
195
- }
196
- 40% {
197
- transform: translateY(-4px);
198
- opacity: 1;
199
- }
200
- 70% {
201
- transform: translateY(2px);
202
- opacity: 0.92;
203
- }
204
- 100% {
205
- transform: translateY(0);
206
- opacity: 0.9;
207
31
  }
208
32
  }
209
-
210
- @keyframes motion-breezy-stream {
211
- 0% {
212
- transform: translateX(-100%);
213
- }
214
- 100% {
215
- transform: translateX(100%);
216
- }
217
- }
218
-
219
- .motion-anim-enter {
220
- animation: motion-enter 150ms cubic-bezier(0, 0, 0.2, 1) both;
221
- }
222
- .motion-anim-exit {
223
- animation: motion-exit 120ms cubic-bezier(0.4, 0, 1, 1) both;
224
- }
225
- .motion-anim-enter-top {
226
- animation: motion-enter-top 150ms cubic-bezier(0, 0, 0.2, 1) both;
227
- }
228
- .motion-anim-exit-top {
229
- animation: motion-exit-top 120ms cubic-bezier(0.4, 0, 1, 1) both;
230
- }
231
- .motion-anim-enter-bottom {
232
- animation: motion-enter-bottom 150ms cubic-bezier(0, 0, 0.2, 1) both;
233
- }
234
- .motion-anim-exit-bottom {
235
- animation: motion-exit-bottom 120ms cubic-bezier(0.4, 0, 1, 1) both;
236
- }
237
- .motion-anim-enter-left {
238
- animation: motion-enter-left 150ms cubic-bezier(0, 0, 0.2, 1) both;
239
- }
240
- .motion-anim-exit-left {
241
- animation: motion-exit-left 120ms cubic-bezier(0.4, 0, 1, 1) both;
242
- }
243
- .motion-anim-enter-right {
244
- animation: motion-enter-right 150ms cubic-bezier(0, 0, 0.2, 1) both;
245
- }
246
- .motion-anim-exit-right {
247
- animation: motion-exit-right 120ms cubic-bezier(0.4, 0, 1, 1) both;
248
- }
249
-
250
- .motion-anim-breezy {
251
- animation: motion-breezy-wave 320ms cubic-bezier(0.32, 0.72, 0.28, 1) forwards;
252
- }
253
- .motion-anim-breezy-loop {
254
- animation: motion-breezy-wave 1.8s cubic-bezier(0.32, 0.72, 0.28, 1) infinite;
255
- }
256
- .motion-anim-breezy-progress {
257
- animation: motion-breezy-stream 1.8s linear infinite;
258
- }
259
-
260
- .motion-anim-breezy-stagger > * {
261
- animation: motion-breezy-wave 1.8s cubic-bezier(0.32, 0.72, 0.28, 1) infinite;
262
- }
263
- .motion-anim-breezy-stagger > *:nth-child(1) {
264
- animation-delay: 0ms;
265
- }
266
- .motion-anim-breezy-stagger > *:nth-child(2) {
267
- animation-delay: 120ms;
268
- }
269
- .motion-anim-breezy-stagger > *:nth-child(3) {
270
- animation-delay: 240ms;
271
- }
272
- .motion-anim-breezy-stagger > *:nth-child(4) {
273
- animation-delay: 360ms;
274
- }
275
- .motion-anim-breezy-stagger > *:nth-child(5) {
276
- animation-delay: 480ms;
277
- }
278
-
279
- @keyframes accordion-down {
33
+ @keyframes zoom-in {
280
34
  from {
281
35
  opacity: 0;
282
- transform: scaleY(0.8);
283
- transform-origin: top;
36
+ transform: scale(0.95);
284
37
  }
285
38
  to {
286
39
  opacity: 1;
287
- transform: scaleY(1);
40
+ transform: scale(1);
288
41
  }
289
42
  }
290
-
291
- @keyframes accordion-up {
43
+ @keyframes zoom-out {
292
44
  from {
293
45
  opacity: 1;
294
- transform: scaleY(1);
295
- transform-origin: top;
46
+ transform: scale(1);
296
47
  }
297
48
  to {
298
49
  opacity: 0;
299
- transform: scaleY(0.8);
50
+ transform: scale(0.95);
300
51
  }
301
52
  }
302
53
 
303
- .animate-accordion-down,
304
- [data-state="open"].animate-accordion-down {
305
- animation: accordion-down 400ms cubic-bezier(0.32, 0.72, 0.28, 1) both;
306
- }
307
-
308
- .animate-accordion-up,
309
- [data-state="closed"].animate-accordion-up {
310
- animation: accordion-up 300ms cubic-bezier(0.32, 0.72, 0.28, 1) both;
311
- }
312
-
313
54
  .animate-in {
314
- animation: motion-enter 150ms cubic-bezier(0, 0, 0.2, 1) both;
55
+ animation:
56
+ fade-in 150ms ease-out,
57
+ zoom-in 150ms ease-out;
315
58
  }
316
59
  .animate-out {
317
- animation: motion-exit 120ms cubic-bezier(0.4, 0, 1, 1) both;
318
- }
319
- .fade-in {
320
- animation: motion-enter 150ms cubic-bezier(0, 0, 0.2, 1) both;
321
- }
322
- .fade-out {
323
- animation: motion-exit 120ms cubic-bezier(0.4, 0, 1, 1) both;
60
+ animation:
61
+ fade-out 120ms ease-in,
62
+ zoom-out 120ms ease-in;
324
63
  }
325
-
326
- /*
327
- * These utility classes exist alongside their opposite (open + closed, or
328
- * visible + hidden) on the same element at all times — only the `data-state`
329
- * attribute value actually changes between renders. Each selector below must
330
- * be qualified with the matching `[data-state="..."]` attribute so only the
331
- * currently-active variant applies; without it, cascade order alone decides
332
- * the winner (whichever rule is declared last), regardless of real state.
333
- */
334
- [data-state="open"].data-\[state\=open\]\:animate-in {
335
- animation: motion-enter 150ms cubic-bezier(0, 0, 0.2, 1) both;
64
+ .fade-in-0 {
65
+ animation-name: fade-in;
336
66
  }
337
- [data-state="closed"].data-\[state\=closed\]\:animate-out {
338
- animation: motion-exit 120ms cubic-bezier(0.4, 0, 1, 1) both;
67
+ .fade-out-0 {
68
+ animation-name: fade-out;
339
69
  }
340
-
341
- [data-state="open"].data-\[state\=open\]\:fade-in-0,
342
- [data-state="open"].data-\[state\=open\]\:fade-in-90,
343
- [data-state="visible"].data-\[state\=visible\]\:fade-in {
344
- animation: motion-enter 150ms cubic-bezier(0, 0, 0.2, 1) both;
70
+ .zoom-in-95 {
71
+ animation-name: zoom-in;
345
72
  }
346
-
347
- [data-state="closed"].data-\[state\=closed\]\:fade-out-0,
348
- [data-state="hidden"].data-\[state\=hidden\]\:fade-out {
349
- animation: motion-exit 120ms cubic-bezier(0.4, 0, 1, 1) both;
350
- }
351
-
352
- [data-state="open"].data-\[state\=open\]\:zoom-in-90,
353
- [data-state="open"].data-\[state\=open\]\:zoom-in-95 {
354
- animation: motion-enter 150ms cubic-bezier(0, 0, 0.2, 1) both;
73
+ .zoom-out-95 {
74
+ animation-name: zoom-out;
355
75
  }
356
- [data-state="closed"].data-\[state\=closed\]\:zoom-out-95 {
357
- animation: motion-exit 120ms cubic-bezier(0.4, 0, 1, 1) both;
76
+ .data-open\:animate-in[data-open],
77
+ .data-open\:animate-in[data-state="open"] {
78
+ animation:
79
+ fade-in 150ms ease-out,
80
+ zoom-in 150ms ease-out;
358
81
  }
359
-
360
- /*
361
- * Same issue as the data-state pairs above: Popover/Tooltip/DropdownMenu
362
- * content applies all four data-[side=...] classes simultaneously (only the
363
- * real `data-side` attribute changes at runtime), so each selector must be
364
- * qualified with the matching [data-side="..."] attribute — otherwise
365
- * cascade order alone picks a winner regardless of actual placement.
366
- */
367
- [data-side="bottom"].data-\[side\=bottom\]\:slide-in-from-top-2 {
368
- animation: motion-enter-top 150ms cubic-bezier(0, 0, 0.2, 1) both;
369
- }
370
- [data-side="top"].data-\[side\=top\]\:slide-in-from-bottom-2 {
371
- animation: motion-enter-bottom 150ms cubic-bezier(0, 0, 0.2, 1) both;
372
- }
373
- [data-side="left"].data-\[side\=left\]\:slide-in-from-right-2 {
374
- animation: motion-enter-right 150ms cubic-bezier(0, 0, 0.2, 1) both;
375
- }
376
- [data-side="right"].data-\[side\=right\]\:slide-in-from-left-2 {
377
- animation: motion-enter-left 150ms cubic-bezier(0, 0, 0.2, 1) both;
378
- }
379
-
380
- .slide-in-from-bottom-10,
381
- .slide-in-from-bottom-0 {
382
- animation: motion-enter-bottom 150ms cubic-bezier(0, 0, 0.2, 1) both;
383
- }
384
- .slide-in-from-left-52 {
385
- animation: motion-enter-left 150ms cubic-bezier(0, 0, 0.2, 1) both;
386
- }
387
- .slide-in-from-right-52 {
388
- animation: motion-enter-right 150ms cubic-bezier(0, 0, 0.2, 1) both;
389
- }
390
- .slide-out-to-left-52 {
391
- animation: motion-exit-left 120ms cubic-bezier(0.4, 0, 1, 1) both;
392
- }
393
- .slide-out-to-right-52 {
394
- animation: motion-exit-right 120ms cubic-bezier(0.4, 0, 1, 1) both;
395
- }
396
-
397
- .note-picker-panel {
398
- transition:
399
- opacity 150ms cubic-bezier(0, 0, 0.2, 1),
400
- transform 150ms cubic-bezier(0, 0, 0.2, 1),
401
- overlay 150ms allow-discrete,
402
- display 150ms allow-discrete;
403
- }
404
-
405
- .note-picker-panel[open] {
406
- opacity: 1;
407
- transform: translateY(0) scale(1);
408
- }
409
-
410
- @starting-style {
411
- .note-picker-panel[open] {
412
- opacity: 0;
413
- transform: translateY(8px) scale(0.98);
414
- }
415
- }
416
-
417
- /*
418
- * View Transitions: opt-in only.
419
- * The browser's default root crossfade animates every diffed pixel of the
420
- * whole page using mix-blend-mode: plus-lighter, which visibly flashes white
421
- * on any element whose color changes during the transition (e.g. a nav item
422
- * going from inactive to active). Disabling the root group's animation makes
423
- * transitions opt-in — only elements with an explicit view-transition-name
424
- * (set inline via style.viewTransitionName) animate; everything else swaps
425
- * instantly with no crossfade artifact.
426
- */
427
- ::view-transition-group(root) {
428
- animation-duration: 0s;
82
+ .data-closed\:animate-out[data-closed],
83
+ .data-closed\:animate-out[data-state="closed"] {
84
+ animation:
85
+ fade-out 120ms ease-in,
86
+ zoom-out 120ms ease-in;
429
87
  }
430
88
 
431
89
  @media (prefers-reduced-motion: reduce) {
432
- ::view-transition-group(*),
433
- ::view-transition-old(*),
434
- ::view-transition-new(*) {
90
+ .animate-in,
91
+ .animate-out,
92
+ [data-open],
93
+ [data-closed] {
435
94
  animation: none !important;
436
95
  }
437
96
  }
@@ -1,11 +1,26 @@
1
- import * as AccordionPrimitive from "@radix-ui/react-accordion";
1
+ import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
2
2
  import { ChevronDown } from "lucide-react";
3
3
  import * as React from "react";
4
4
 
5
5
  import { cn } from "../../lib/utils";
6
6
 
7
- function Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
8
- return <AccordionPrimitive.Root data-slot="accordion" {...props} className="w-full" />;
7
+ function Accordion({
8
+ type,
9
+ collapsible: _collapsible,
10
+ className,
11
+ ...props
12
+ }: React.ComponentProps<typeof AccordionPrimitive.Root> & {
13
+ type?: "single" | "multiple";
14
+ collapsible?: boolean;
15
+ }) {
16
+ return (
17
+ <AccordionPrimitive.Root
18
+ data-slot="accordion"
19
+ multiple={type === "multiple"}
20
+ {...props}
21
+ className={cn("w-full", className)}
22
+ />
23
+ );
9
24
  }
10
25
 
11
26
  function AccordionItem({
@@ -15,10 +30,7 @@ function AccordionItem({
15
30
  return (
16
31
  <AccordionPrimitive.Item
17
32
  data-slot="accordion-item"
18
- className={cn(
19
- "border-default bg-canvas mb-2 overflow-hidden rounded-md border last:mb-0",
20
- className,
21
- )}
33
+ className={cn("mb-2 overflow-hidden rounded-md border last:mb-0", className)}
22
34
  {...props}
23
35
  />
24
36
  );
@@ -50,15 +62,15 @@ function AccordionContent({
50
62
  className,
51
63
  children,
52
64
  ...props
53
- }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
65
+ }: React.ComponentProps<typeof AccordionPrimitive.Panel>) {
54
66
  return (
55
- <AccordionPrimitive.Content
67
+ <AccordionPrimitive.Panel
56
68
  data-slot="accordion-content"
57
69
  className="animate-accordion-down overflow-hidden text-sm"
58
70
  {...props}
59
71
  >
60
72
  <div className={cn("px-4 pt-2 pb-3", className)}>{children}</div>
61
- </AccordionPrimitive.Content>
73
+ </AccordionPrimitive.Panel>
62
74
  );
63
75
  }
64
76
 
@@ -0,0 +1,38 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { FilterChip } from "./filter-chip";
4
+
5
+ const meta = {
6
+ title: "DataDisplay/FilterChip",
7
+ component: FilterChip,
8
+ tags: ["autodocs"],
9
+ args: {
10
+ onRemove: () => {},
11
+ },
12
+ } satisfies Meta<typeof FilterChip>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {
18
+ args: { label: "Status: Active" },
19
+ };
20
+
21
+ export const Editable: Story = {
22
+ args: { label: "Status: Active", onClick: () => {} },
23
+ };
24
+
25
+ export const LongLabel: Story = {
26
+ args: { label: "Created between Jan 1, 2026 and Jul 24, 2026" },
27
+ };
28
+
29
+ export const Group: Story = {
30
+ args: { label: "Filters" },
31
+ render: () => (
32
+ <div className="flex flex-wrap gap-2">
33
+ <FilterChip label="Status: Active" onRemove={() => {}} onClick={() => {}} />
34
+ <FilterChip label="Team: Design" onRemove={() => {}} onClick={() => {}} />
35
+ <FilterChip label="Archived" onRemove={() => {}} />
36
+ </div>
37
+ ),
38
+ };