@ponti-studios/ui 2.0.1 → 3.0.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 (319) hide show
  1. package/README.md +36 -3
  2. package/dist/auth/index.d.ts +3 -0
  3. package/dist/auth/index.js +3 -0
  4. package/dist/components/data-display/accordion.d.ts +10 -0
  5. package/dist/components/data-display/accordion.js +53 -0
  6. package/{src/components/data-display/index.ts → dist/components/data-display/index.d.ts} +1 -10
  7. package/dist/components/data-display/index.js +8 -0
  8. package/dist/components/data-display/metric-card.d.ts +9 -0
  9. package/dist/components/data-display/metric-card.js +25 -0
  10. package/dist/components/data-display/sort-controls.d.ts +13 -0
  11. package/dist/components/data-display/sort-controls.js +57 -0
  12. package/dist/components/data-display/sort-row.d.ts +10 -0
  13. package/dist/components/data-display/sort-row.js +52 -0
  14. package/dist/components/data-display/table.d.ts +10 -0
  15. package/dist/components/data-display/table.js +57 -0
  16. package/dist/components/feedback/alert.d.ts +9 -0
  17. package/dist/components/feedback/alert.js +38 -0
  18. package/dist/components/feedback/empty-state.d.ts +18 -0
  19. package/dist/components/feedback/empty-state.js +36 -0
  20. package/{src/components/feedback/index.ts → dist/components/feedback/index.d.ts} +2 -0
  21. package/dist/components/feedback/index.js +7 -0
  22. package/dist/components/feedback/progress.d.ts +6 -0
  23. package/dist/components/feedback/progress.js +25 -0
  24. package/dist/components/feedback/skeleton.d.ts +2 -0
  25. package/dist/components/feedback/skeleton.js +11 -0
  26. package/dist/components/feedback/spinner.d.ts +14 -0
  27. package/dist/components/feedback/spinner.js +27 -0
  28. package/dist/components/feedback/update-guard.d.ts +13 -0
  29. package/dist/components/feedback/update-guard.js +38 -0
  30. package/dist/components/filters/active-filters-bar.d.ts +14 -0
  31. package/dist/components/filters/active-filters-bar.js +44 -0
  32. package/dist/components/filters/filter-chip.d.ts +11 -0
  33. package/dist/components/filters/filter-chip.js +33 -0
  34. package/dist/components/filters/filter-select.d.ts +17 -0
  35. package/dist/components/filters/filter-select.js +46 -0
  36. package/dist/components/filters/index.js +4 -0
  37. package/dist/components/forms/checkbox.d.ts +4 -0
  38. package/dist/components/forms/checkbox.js +24 -0
  39. package/dist/components/forms/date-month-select.d.ts +11 -0
  40. package/dist/components/forms/date-month-select.js +47 -0
  41. package/dist/components/forms/date-picker.d.ts +60 -0
  42. package/dist/components/forms/date-picker.js +193 -0
  43. package/dist/components/forms/date-utils.d.ts +14 -0
  44. package/dist/components/forms/date-utils.js +18 -0
  45. package/dist/components/forms/drop-zone.d.ts +42 -0
  46. package/dist/components/forms/drop-zone.js +184 -0
  47. package/{src/components/forms/index.ts → dist/components/forms/index.d.ts} +3 -9
  48. package/dist/components/forms/index.js +17 -0
  49. package/dist/components/forms/input.d.ts +3 -0
  50. package/dist/components/forms/input.js +15 -0
  51. package/dist/components/forms/label.d.ts +3 -0
  52. package/dist/components/forms/label.js +14 -0
  53. package/dist/components/forms/passkey-management.d.ts +14 -0
  54. package/dist/components/forms/passkey-management.js +132 -0
  55. package/dist/components/forms/radio-group.d.ts +6 -0
  56. package/dist/components/forms/radio-group.js +28 -0
  57. package/dist/components/forms/select.d.ts +18 -0
  58. package/dist/components/forms/select.js +103 -0
  59. package/dist/components/forms/slider.d.ts +9 -0
  60. package/dist/components/forms/slider.js +25 -0
  61. package/dist/components/forms/stepper.d.ts +15 -0
  62. package/dist/components/forms/stepper.js +41 -0
  63. package/dist/components/forms/switch.d.ts +6 -0
  64. package/dist/components/forms/switch.js +21 -0
  65. package/dist/components/forms/text-field.d.ts +15 -0
  66. package/dist/components/forms/text-field.js +46 -0
  67. package/dist/components/forms/text-field.types.d.ts +8 -0
  68. package/dist/components/forms/textarea.d.ts +3 -0
  69. package/dist/components/forms/textarea.js +14 -0
  70. package/dist/components/layout/index.js +5 -0
  71. package/dist/components/layout/particle-background.d.ts +19 -0
  72. package/dist/components/layout/particle-background.js +171 -0
  73. package/dist/components/layout/scroll-area.d.ts +9 -0
  74. package/dist/components/layout/scroll-area.js +35 -0
  75. package/dist/components/layout/section-intro.d.ts +12 -0
  76. package/dist/components/layout/section-intro.js +28 -0
  77. package/dist/components/layout/surface-panel.d.ts +7 -0
  78. package/dist/components/layout/surface-panel.js +11 -0
  79. package/dist/components/navigation/app-navigation.d.ts +67 -0
  80. package/dist/components/navigation/app-navigation.js +106 -0
  81. package/{src/components/navigation/index.ts → dist/components/navigation/index.d.ts} +1 -7
  82. package/dist/components/navigation/index.js +4 -0
  83. package/dist/components/navigation/pagination-controls.d.ts +7 -0
  84. package/dist/components/navigation/pagination-controls.js +50 -0
  85. package/dist/components/navigation/tabs.d.ts +7 -0
  86. package/dist/components/navigation/tabs.js +35 -0
  87. package/dist/components/overlays/bottom-sheet.d.ts +20 -0
  88. package/dist/components/overlays/bottom-sheet.js +101 -0
  89. package/dist/components/overlays/command.d.ts +29 -0
  90. package/dist/components/overlays/command.js +123 -0
  91. package/dist/components/overlays/dialog.d.ts +30 -0
  92. package/dist/components/overlays/dialog.js +105 -0
  93. package/dist/components/overlays/dropdown-menu.d.ts +29 -0
  94. package/dist/components/overlays/dropdown-menu.js +128 -0
  95. package/dist/components/overlays/index.d.ts +7 -0
  96. package/dist/components/overlays/index.js +8 -0
  97. package/dist/components/overlays/popover.d.ts +14 -0
  98. package/dist/components/overlays/popover.js +56 -0
  99. package/dist/components/overlays/sheet.d.ts +27 -0
  100. package/dist/components/overlays/sheet.js +81 -0
  101. package/dist/components/overlays/tooltip.d.ts +10 -0
  102. package/dist/components/overlays/tooltip.js +23 -0
  103. package/dist/components/primitives/avatar.d.ts +14 -0
  104. package/dist/components/primitives/avatar.js +72 -0
  105. package/dist/components/primitives/badge.d.ts +7 -0
  106. package/dist/components/primitives/badge.js +27 -0
  107. package/dist/components/primitives/button.d.ts +13 -0
  108. package/dist/components/primitives/button.js +113 -0
  109. package/{src/components/primitives/button.types.ts → dist/components/primitives/button.types.d.ts} +4 -6
  110. package/dist/components/primitives/card.d.ts +9 -0
  111. package/dist/components/primitives/card.js +56 -0
  112. package/{src/components/primitives/index.ts → dist/components/primitives/index.d.ts} +1 -9
  113. package/dist/components/primitives/index.js +7 -0
  114. package/dist/components/primitives/segmented-control.d.ts +20 -0
  115. package/dist/components/primitives/segmented-control.js +27 -0
  116. package/dist/components/primitives/separator.d.ts +4 -0
  117. package/dist/components/primitives/separator.js +16 -0
  118. package/dist/constants/chart-colors.d.ts +33 -0
  119. package/dist/constants/chart-colors.js +31 -0
  120. package/dist/hooks/index.js +9 -0
  121. package/{src/hooks/sort.types.ts → dist/hooks/sort.types.d.ts} +2 -4
  122. package/dist/hooks/use-api-client.d.ts +30 -0
  123. package/dist/hooks/use-api-client.js +106 -0
  124. package/dist/hooks/use-debounced-value.d.ts +1 -0
  125. package/dist/hooks/use-debounced-value.js +12 -0
  126. package/dist/hooks/use-derived-filter-state.d.ts +20 -0
  127. package/dist/hooks/use-derived-filter-state.js +39 -0
  128. package/dist/hooks/use-filter-state.d.ts +12 -0
  129. package/dist/hooks/use-filter-state.js +37 -0
  130. package/dist/hooks/use-media-query.d.ts +6 -0
  131. package/dist/hooks/use-media-query.js +27 -0
  132. package/dist/hooks/use-mobile.d.ts +1 -0
  133. package/dist/hooks/use-mobile.js +14 -0
  134. package/dist/hooks/use-sort.d.ts +11 -0
  135. package/dist/hooks/use-sort.js +14 -0
  136. package/{src/index.ts → dist/index.d.ts} +4 -32
  137. package/dist/index.js +15 -0
  138. package/dist/integrations/service-worker.d.ts +2 -0
  139. package/dist/integrations/service-worker.js +2 -0
  140. package/dist/lib/auth.d.ts +2 -0
  141. package/dist/lib/auth.js +7 -0
  142. package/dist/lib/clipboard.d.ts +1 -0
  143. package/dist/lib/clipboard.js +10 -0
  144. package/dist/lib/index.js +4 -0
  145. package/{src/lib/slot.tsx → dist/lib/slot.d.ts} +2 -15
  146. package/dist/lib/slot.js +14 -0
  147. package/dist/lib/utils.d.ts +2 -0
  148. package/dist/lib/utils.js +8 -0
  149. package/dist/native/components.d.ts +41 -0
  150. package/dist/native/components.js +135 -0
  151. package/dist/native/index.js +3 -0
  152. package/dist/native/theme.d.ts +19 -0
  153. package/dist/native/theme.js +58 -0
  154. package/dist/styles/tokens/css-vars.d.ts +7 -0
  155. package/dist/styles/tokens/css-vars.js +5 -0
  156. package/dist/styles/tokens/generated/dark.colors.generated.d.ts +40 -0
  157. package/dist/styles/tokens/generated/dark.colors.generated.js +39 -0
  158. package/dist/styles/tokens/generated/foundations.generated.d.ts +323 -0
  159. package/dist/styles/tokens/generated/foundations.generated.js +453 -0
  160. package/dist/styles/tokens/generated/light.colors.generated.d.ts +40 -0
  161. package/dist/styles/tokens/generated/light.colors.generated.js +39 -0
  162. package/dist/styles/tokens/index.d.ts +317 -0
  163. package/dist/styles/tokens/index.js +14 -0
  164. package/dist/styles.css +1 -1
  165. package/dist/translations/en.d.ts +34 -0
  166. package/dist/translations/index.d.ts +39 -0
  167. package/dist/translations/ui-provider.d.ts +9 -0
  168. package/dist/translations/ui-provider.js +16 -0
  169. package/dist/types/upload.d.ts +36 -0
  170. package/package.json +50 -33
  171. package/src/components/data-display/accordion.stories.tsx +0 -57
  172. package/src/components/data-display/accordion.tsx +0 -86
  173. package/src/components/data-display/metric-card.stories.tsx +0 -48
  174. package/src/components/data-display/metric-card.tsx +0 -20
  175. package/src/components/data-display/sort-controls.stories.tsx +0 -45
  176. package/src/components/data-display/sort-controls.tsx +0 -96
  177. package/src/components/data-display/sort-row.tsx +0 -88
  178. package/src/components/data-display/table.stories.tsx +0 -75
  179. package/src/components/data-display/table.tsx +0 -80
  180. package/src/components/feedback/alert.stories.tsx +0 -51
  181. package/src/components/feedback/alert.tsx +0 -64
  182. package/src/components/feedback/empty-state.tsx +0 -76
  183. package/src/components/feedback/progress.stories.tsx +0 -54
  184. package/src/components/feedback/progress.tsx +0 -41
  185. package/src/components/feedback/skeleton.stories.tsx +0 -36
  186. package/src/components/feedback/skeleton.tsx +0 -7
  187. package/src/components/feedback/spinner.stories.tsx +0 -52
  188. package/src/components/feedback/spinner.tsx +0 -47
  189. package/src/components/feedback/update-guard.stories.tsx +0 -28
  190. package/src/components/feedback/update-guard.tsx +0 -57
  191. package/src/components/filters/active-filters-bar.stories.tsx +0 -114
  192. package/src/components/filters/active-filters-bar.tsx +0 -61
  193. package/src/components/filters/filter-chip.tsx +0 -57
  194. package/src/components/filters/filter-select.stories.tsx +0 -71
  195. package/src/components/filters/filter-select.tsx +0 -68
  196. package/src/components/forms/checkbox.stories.tsx +0 -35
  197. package/src/components/forms/checkbox.tsx +0 -38
  198. package/src/components/forms/date-month-select.stories.tsx +0 -26
  199. package/src/components/forms/date-month-select.tsx +0 -79
  200. package/src/components/forms/date-picker.stories.tsx +0 -188
  201. package/src/components/forms/date-picker.tsx +0 -303
  202. package/src/components/forms/date-utils.ts +0 -37
  203. package/src/components/forms/drop-zone.stories.tsx +0 -140
  204. package/src/components/forms/drop-zone.tsx +0 -298
  205. package/src/components/forms/input.stories.tsx +0 -47
  206. package/src/components/forms/input.tsx +0 -24
  207. package/src/components/forms/label.stories.tsx +0 -16
  208. package/src/components/forms/label.tsx +0 -25
  209. package/src/components/forms/passkey-management.stories.tsx +0 -85
  210. package/src/components/forms/passkey-management.tsx +0 -168
  211. package/src/components/forms/radio-group.stories.tsx +0 -46
  212. package/src/components/forms/radio-group.tsx +0 -44
  213. package/src/components/forms/select.stories.tsx +0 -207
  214. package/src/components/forms/select.tsx +0 -182
  215. package/src/components/forms/slider.stories.tsx +0 -70
  216. package/src/components/forms/slider.tsx +0 -50
  217. package/src/components/forms/stepper.stories.tsx +0 -111
  218. package/src/components/forms/stepper.tsx +0 -66
  219. package/src/components/forms/switch.stories.tsx +0 -144
  220. package/src/components/forms/switch.tsx +0 -39
  221. package/src/components/forms/text-field.stories.tsx +0 -87
  222. package/src/components/forms/text-field.tsx +0 -75
  223. package/src/components/forms/text-field.types.ts +0 -9
  224. package/src/components/forms/textarea.stories.tsx +0 -150
  225. package/src/components/forms/textarea.tsx +0 -22
  226. package/src/components/layout/particle-background.stories.tsx +0 -38
  227. package/src/components/layout/particle-background.tsx +0 -378
  228. package/src/components/layout/scroll-area.stories.tsx +0 -91
  229. package/src/components/layout/scroll-area.tsx +0 -79
  230. package/src/components/layout/section-intro.stories.tsx +0 -29
  231. package/src/components/layout/section-intro.tsx +0 -31
  232. package/src/components/layout/surface-panel.stories.tsx +0 -32
  233. package/src/components/layout/surface-panel.tsx +0 -23
  234. package/src/components/navigation/app-navigation.stories.tsx +0 -217
  235. package/src/components/navigation/app-navigation.tsx +0 -234
  236. package/src/components/navigation/pagination-controls.stories.tsx +0 -41
  237. package/src/components/navigation/pagination-controls.tsx +0 -46
  238. package/src/components/navigation/tabs.stories.tsx +0 -48
  239. package/src/components/navigation/tabs.tsx +0 -52
  240. package/src/components/overlays/bottom-sheet.stories.tsx +0 -52
  241. package/src/components/overlays/bottom-sheet.tsx +0 -179
  242. package/src/components/overlays/command.stories.tsx +0 -65
  243. package/src/components/overlays/command.tsx +0 -227
  244. package/src/components/overlays/dialog.stories.tsx +0 -148
  245. package/src/components/overlays/dialog.tsx +0 -203
  246. package/src/components/overlays/dropdown-menu.stories.tsx +0 -74
  247. package/src/components/overlays/dropdown-menu.tsx +0 -236
  248. package/src/components/overlays/index.ts +0 -56
  249. package/src/components/overlays/popover.stories.tsx +0 -51
  250. package/src/components/overlays/popover.tsx +0 -78
  251. package/src/components/overlays/sheet.stories.tsx +0 -68
  252. package/src/components/overlays/sheet.tsx +0 -155
  253. package/src/components/overlays/tooltip.stories.tsx +0 -29
  254. package/src/components/overlays/tooltip.tsx +0 -41
  255. package/src/components/primitives/avatar-placeholder.svg +0 -13
  256. package/src/components/primitives/avatar.stories.tsx +0 -72
  257. package/src/components/primitives/avatar.tsx +0 -136
  258. package/src/components/primitives/badge.stories.tsx +0 -43
  259. package/src/components/primitives/badge.tsx +0 -43
  260. package/src/components/primitives/button.stories.tsx +0 -113
  261. package/src/components/primitives/button.tsx +0 -131
  262. package/src/components/primitives/card.stories.tsx +0 -59
  263. package/src/components/primitives/card.tsx +0 -66
  264. package/src/components/primitives/segmented-control.stories.tsx +0 -54
  265. package/src/components/primitives/segmented-control.tsx +0 -73
  266. package/src/components/primitives/separator.stories.tsx +0 -34
  267. package/src/components/primitives/separator.tsx +0 -27
  268. package/src/constants/chart-colors.ts +0 -39
  269. package/src/env.d.ts +0 -9
  270. package/src/hooks/use-api-client.ts +0 -122
  271. package/src/hooks/use-debounced-value.ts +0 -12
  272. package/src/hooks/use-derived-filter-state.ts +0 -70
  273. package/src/hooks/use-filter-state.ts +0 -66
  274. package/src/hooks/use-media-query.ts +0 -69
  275. package/src/hooks/use-mobile.tsx +0 -19
  276. package/src/hooks/use-sort.ts +0 -20
  277. package/src/lib/auth.ts +0 -5
  278. package/src/lib/clipboard.ts +0 -8
  279. package/src/lib/utils.ts +0 -6
  280. package/src/native/components.tsx +0 -198
  281. package/src/native/theme.tsx +0 -69
  282. package/src/storybook/controls.ts +0 -117
  283. package/src/storybook/options.ts +0 -52
  284. package/src/styles/build.ts +0 -1
  285. package/src/styles/index.css +0 -423
  286. package/src/styles/nokia.css +0 -36
  287. package/src/styles/package.css +0 -3
  288. package/src/styles/tokens/chart-colors.stories.tsx +0 -189
  289. package/src/styles/tokens/color-systems.stories.tsx +0 -190
  290. package/src/styles/tokens/css-vars.ts +0 -15
  291. package/src/styles/tokens/generated/dark-class.css +0 -39
  292. package/src/styles/tokens/generated/dark.colors.generated.ts +0 -39
  293. package/src/styles/tokens/generated/dark.css +0 -39
  294. package/src/styles/tokens/generated/foundations.css +0 -149
  295. package/src/styles/tokens/generated/foundations.generated.ts +0 -454
  296. package/src/styles/tokens/generated/light.colors.generated.ts +0 -39
  297. package/src/styles/tokens/generated/light.css +0 -39
  298. package/src/styles/tokens/index.ts +0 -70
  299. package/src/styles/tokens/layout.stories.tsx +0 -50
  300. package/src/styles/tokens/scripts/check-foundations.mjs +0 -126
  301. package/src/styles/tokens/scripts/check-source.mjs +0 -53
  302. package/src/styles/tokens/source/dark.tokens.json +0 -138
  303. package/src/styles/tokens/source/foundations.tokens.json +0 -344
  304. package/src/styles/tokens/source/light.tokens.json +0 -138
  305. package/src/styles/tokens/style-dictionary.dark-class.json +0 -16
  306. package/src/styles/tokens/style-dictionary.dark-storybook.json +0 -16
  307. package/src/styles/tokens/style-dictionary.dark.json +0 -21
  308. package/src/styles/tokens/style-dictionary.foundations.json +0 -21
  309. package/src/styles/tokens/style-dictionary.light-storybook.json +0 -16
  310. package/src/styles/tokens/style-dictionary.light.json +0 -21
  311. package/src/styles/tokens/typography.stories.tsx +0 -157
  312. package/src/translations/en.ts +0 -34
  313. package/src/translations/index.ts +0 -32
  314. package/src/types/upload.ts +0 -42
  315. /package/{src/components/filters/index.ts → dist/components/filters/index.d.ts} +0 -0
  316. /package/{src/components/layout/index.ts → dist/components/layout/index.d.ts} +0 -0
  317. /package/{src/hooks/index.ts → dist/hooks/index.d.ts} +0 -0
  318. /package/{src/lib/index.ts → dist/lib/index.d.ts} +0 -0
  319. /package/{src/native/index.ts → dist/native/index.d.ts} +0 -0
@@ -1,38 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import { ParticleBackground } from "./particle-background";
4
-
5
- const meta = {
6
- title: "Layout/ParticleBackground",
7
- component: ParticleBackground,
8
- tags: ["autodocs"],
9
- } satisfies Meta<typeof ParticleBackground>;
10
-
11
- export default meta;
12
- type Story = StoryObj<typeof meta>;
13
-
14
- export const Default: Story = {
15
- render: (args) => (
16
- <div className="bg-background relative h-80 w-[640px] overflow-hidden">
17
- <ParticleBackground {...args} />
18
- </div>
19
- ),
20
- };
21
-
22
- export const Static: Story = {
23
- args: { interactive: false },
24
- render: (args) => (
25
- <div className="bg-background relative h-80 w-[640px] overflow-hidden">
26
- <ParticleBackground {...args} />
27
- </div>
28
- ),
29
- };
30
-
31
- export const Disabled: Story = {
32
- args: { enabled: false },
33
- render: (args) => (
34
- <div className="bg-background relative h-80 w-[640px] overflow-hidden">
35
- <ParticleBackground {...args} />
36
- </div>
37
- ),
38
- };
@@ -1,378 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- import { useMediaQuery } from "../../hooks/use-media-query";
6
- import { cn } from "../../lib/utils";
7
-
8
- interface Particle {
9
- x: number;
10
- y: number;
11
- vx: number;
12
- vy: number;
13
- opacity: number;
14
- depth: number;
15
- twinklePhase: number;
16
- twinkleSpeed: number;
17
- }
18
-
19
- export interface ParticleBackgroundPalette {
20
- particle: string;
21
- link: string;
22
- }
23
-
24
- export interface ParticleBackgroundProps extends React.ComponentProps<"div"> {
25
- enabled?: boolean;
26
- interactive?: boolean;
27
- particleCount?: number;
28
- particleDensity?: number;
29
- maxLinkDistance?: number;
30
- attractRadius?: number;
31
- attractStrength?: number;
32
- velocity?: number;
33
- showGradient?: boolean;
34
- palette?: ParticleBackgroundPalette;
35
- }
36
-
37
- const DEFAULT_PARTICLE_COUNT = 320;
38
- const DEFAULT_PARTICLE_DENSITY = 2800;
39
- const DEFAULT_LINK_DISTANCE = 90;
40
- const DEFAULT_ATTRACT_STRENGTH = 0.003;
41
- const DEFAULT_ATTRACT_RADIUS = 150;
42
- const DEFAULT_VELOCITY = 0.06;
43
- const PARALLAX_STRENGTH = 0.015;
44
- const TWINKLE_MIN_SPEED = 0.0002;
45
- const TWINKLE_MAX_SPEED = 0.0008;
46
- const PARTICLE_BASE_OPACITY = 0.2;
47
-
48
- const DEFAULT_PALETTE = {
49
- particle: "15,23,42",
50
- link: "15,23,42",
51
- };
52
-
53
- function readCssVar(name: string, fallback: string) {
54
- if (typeof window === "undefined") return fallback;
55
- const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
56
- return value || fallback;
57
- }
58
-
59
- function toRgbTriplet(color: string): string {
60
- if (color.startsWith("#")) {
61
- let hex = color.slice(1);
62
- if (hex.length === 3) {
63
- hex = hex
64
- .split("")
65
- .map((c) => c + c)
66
- .join("");
67
- }
68
- const num = Number.parseInt(hex, 16);
69
- return `${(num >> 16) & 255},${(num >> 8) & 255},${num & 255}`;
70
- }
71
-
72
- const match = color.match(/rgba?\(([^)]+)\)/);
73
- if (match) {
74
- return match[1]
75
- .split(",")
76
- .slice(0, 3)
77
- .map((part) => part.trim())
78
- .join(",");
79
- }
80
-
81
- return DEFAULT_PALETTE.particle;
82
- }
83
-
84
- function useThemePalette(): ParticleBackgroundPalette {
85
- const [palette, setPalette] = React.useState<ParticleBackgroundPalette>(DEFAULT_PALETTE);
86
-
87
- React.useEffect(() => {
88
- const resolve = () => {
89
- const accent = toRgbTriplet(readCssVar("--color-primary", "#0969da"));
90
- const foreground = toRgbTriplet(readCssVar("--color-foreground", "#25292e"));
91
- setPalette({ particle: accent, link: foreground });
92
- };
93
-
94
- resolve();
95
-
96
- const media = window.matchMedia("(prefers-color-scheme: dark)");
97
- media.addEventListener("change", resolve);
98
-
99
- return () => {
100
- media.removeEventListener("change", resolve);
101
- };
102
- }, []);
103
-
104
- return palette;
105
- }
106
-
107
- function useResolvedTheme() {
108
- const [resolvedTheme, setResolvedTheme] = React.useState<"light" | "dark">("light");
109
-
110
- React.useEffect(() => {
111
- const media = window.matchMedia("(prefers-color-scheme: dark)");
112
- const resolve = () => setResolvedTheme(media.matches ? "dark" : "light");
113
-
114
- resolve();
115
- media.addEventListener("change", resolve);
116
-
117
- return () => {
118
- media.removeEventListener("change", resolve);
119
- };
120
- }, []);
121
-
122
- return resolvedTheme;
123
- }
124
-
125
- /** Animated canvas background of drifting, linked particles. Skips rendering under `prefers-reduced-motion`. */
126
- export function ParticleBackground({
127
- className,
128
- enabled = true,
129
- interactive = true,
130
- particleCount,
131
- particleDensity = DEFAULT_PARTICLE_DENSITY,
132
- maxLinkDistance = DEFAULT_LINK_DISTANCE,
133
- attractRadius = DEFAULT_ATTRACT_RADIUS,
134
- attractStrength = DEFAULT_ATTRACT_STRENGTH,
135
- velocity = DEFAULT_VELOCITY,
136
- showGradient: _showGradient = true,
137
- palette,
138
- ...props
139
- }: ParticleBackgroundProps) {
140
- const prefersReduced = useMediaQuery("(prefers-reduced-motion: reduce)");
141
- const canvasRef = React.useRef<HTMLCanvasElement>(null);
142
- const mouse = React.useRef({ x: -9999, y: -9999 });
143
- const particles = React.useRef<Particle[]>([]);
144
- const raf = React.useRef<number>(0);
145
- const scrollOffset = React.useRef(typeof window === "undefined" ? 0 : window.scrollY);
146
- const resolvedTheme = useResolvedTheme();
147
- const themePalette = useThemePalette();
148
- const resolvedPalette = palette ?? themePalette;
149
-
150
- React.useEffect(() => {
151
- if (prefersReduced || !enabled) return;
152
-
153
- const canvasEl = canvasRef.current;
154
- if (!canvasEl) return;
155
-
156
- const ctx = canvasEl.getContext("2d");
157
- if (!ctx) return;
158
-
159
- const canvas = canvasEl;
160
- const context = ctx;
161
-
162
- function resize() {
163
- const rect = canvas.getBoundingClientRect();
164
- const dpr = window.devicePixelRatio || 1;
165
-
166
- canvas.width = Math.max(1, Math.round(rect.width * dpr));
167
- canvas.height = Math.max(1, Math.round(rect.height * dpr));
168
- context.setTransform(dpr, 0, 0, dpr, 0, 0);
169
-
170
- const targetCount =
171
- particleCount ??
172
- Math.max(
173
- 80,
174
- Math.min(
175
- DEFAULT_PARTICLE_COUNT,
176
- Math.round((rect.width * rect.height) / particleDensity),
177
- ),
178
- );
179
-
180
- particles.current = Array.from({ length: targetCount }, () => {
181
- const depth = Math.random();
182
-
183
- return {
184
- x: Math.random() * rect.width,
185
- y: Math.random() * rect.height,
186
- vx: (Math.random() - 0.5) * velocity * (0.4 + depth * 0.8),
187
- vy: (Math.random() - 0.5) * velocity * (0.4 + depth * 0.8),
188
- opacity: PARTICLE_BASE_OPACITY * (0.5 + depth * 0.5),
189
- depth,
190
- twinklePhase: Math.random() * Math.PI * 2,
191
- twinkleSpeed: TWINKLE_MIN_SPEED + Math.random() * (TWINKLE_MAX_SPEED - TWINKLE_MIN_SPEED),
192
- };
193
- });
194
- }
195
-
196
- let isRunning = false;
197
- let inView = true;
198
- let tabVisible = !document.hidden;
199
- const renderPositions: { x: number; y: number }[] = [];
200
-
201
- function draw(time: number) {
202
- const width = canvas.clientWidth;
203
- const height = canvas.clientHeight;
204
-
205
- context.clearRect(0, 0, width, height);
206
- context.lineCap = "round";
207
-
208
- const ps = particles.current;
209
- const parallaxY = scrollOffset.current * PARALLAX_STRENGTH;
210
-
211
- for (let i = 0; i < ps.length; i++) {
212
- const p = ps[i];
213
-
214
- const mdx = mouse.current.x - p.x;
215
- const mdy = mouse.current.y - p.y;
216
- const mdist = Math.sqrt(mdx * mdx + mdy * mdy);
217
-
218
- if (interactive && mdist < attractRadius) {
219
- const force = (1 - mdist / attractRadius) * attractStrength;
220
- p.vx += mdx * force;
221
- p.vy += mdy * force;
222
- }
223
-
224
- p.vx *= 0.98;
225
- p.vy *= 0.98;
226
-
227
- p.x += p.vx;
228
- p.y += p.vy;
229
-
230
- if (p.x < 0) p.x = width;
231
- if (p.x > width) p.x = 0;
232
- if (p.y < 0) p.y = height;
233
- if (p.y > height) p.y = 0;
234
-
235
- const renderY = (((p.y + parallaxY * p.depth) % height) + height) % height;
236
- renderPositions[i] = { x: p.x, y: renderY };
237
-
238
- const twinkle = 0.6 + 0.4 * Math.sin(time * p.twinkleSpeed + p.twinklePhase);
239
- const radius = 0.6 + p.depth * 1.4;
240
-
241
- context.beginPath();
242
- context.arc(p.x, renderY, radius, 0, Math.PI * 2);
243
- context.fillStyle = `rgba(${resolvedPalette.particle},${p.opacity * twinkle})`;
244
- context.fill();
245
-
246
- for (let j = i + 1; j < ps.length; j++) {
247
- const q = ps[j];
248
- const qRender = renderPositions[j] ?? { x: q.x, y: q.y };
249
- const dx = p.x - q.x;
250
- const dy = renderY - qRender.y;
251
- const dist = Math.sqrt(dx * dx + dy * dy);
252
-
253
- if (dist < maxLinkDistance) {
254
- const depthFactor = (p.depth + q.depth) / 2;
255
-
256
- // Links are more transparent in dark mode to reduce contrast and visual noise, and slightly more opaque in light mode to add some subtlety.
257
- const alpha =
258
- (1 - dist / maxLinkDistance) *
259
- (resolvedTheme === "dark" ? 0.015 : 0.01) *
260
- depthFactor;
261
- context.beginPath();
262
- context.moveTo(p.x, renderY);
263
- context.lineTo(qRender.x, qRender.y);
264
- context.strokeStyle = `rgba(${resolvedPalette.link},${alpha})`;
265
- context.lineWidth = 0.5;
266
- context.stroke();
267
- }
268
- }
269
- }
270
-
271
- if (isRunning) {
272
- raf.current = requestAnimationFrame(draw);
273
- }
274
- }
275
-
276
- function syncRunning() {
277
- const shouldRun = inView && tabVisible;
278
-
279
- if (shouldRun && !isRunning) {
280
- isRunning = true;
281
- raf.current = requestAnimationFrame(draw);
282
- } else if (!shouldRun && isRunning) {
283
- isRunning = false;
284
- cancelAnimationFrame(raf.current);
285
- }
286
- }
287
-
288
- // Resizing the canvas element clears its pixels, so repaint a frame
289
- // whenever the loop isn't already running to redraw for it.
290
- function handleResize() {
291
- resize();
292
- if (!isRunning) {
293
- draw(performance.now());
294
- }
295
- }
296
-
297
- const ro = new ResizeObserver(handleResize);
298
- ro.observe(canvas);
299
- resize();
300
- // Paint one frame immediately so the canvas is never left blank while
301
- // waiting on the intersection/visibility observers to report in.
302
- draw(performance.now());
303
- syncRunning();
304
-
305
- const onMouseMove = (event: MouseEvent) => {
306
- const rect = canvas.getBoundingClientRect();
307
- mouse.current = { x: event.clientX - rect.left, y: event.clientY - rect.top };
308
- };
309
-
310
- const onMouseLeave = () => {
311
- mouse.current = { x: -9999, y: -9999 };
312
- };
313
-
314
- if (interactive) {
315
- canvas.addEventListener("mousemove", onMouseMove);
316
- canvas.addEventListener("mouseleave", onMouseLeave);
317
- document.addEventListener("mousemove", onMouseMove);
318
- }
319
-
320
- const onScroll = () => {
321
- scrollOffset.current = window.scrollY;
322
- };
323
-
324
- window.addEventListener("scroll", onScroll, { passive: true });
325
-
326
- const io = new IntersectionObserver(
327
- ([entry]) => {
328
- inView = entry?.isIntersecting ?? true;
329
- syncRunning();
330
- },
331
- { threshold: 0 },
332
- );
333
- io.observe(canvas);
334
-
335
- const onVisibilityChange = () => {
336
- tabVisible = !document.hidden;
337
- syncRunning();
338
- };
339
-
340
- document.addEventListener("visibilitychange", onVisibilityChange);
341
-
342
- return () => {
343
- isRunning = false;
344
- cancelAnimationFrame(raf.current);
345
- ro.disconnect();
346
- io.disconnect();
347
- document.removeEventListener("mousemove", onMouseMove);
348
- document.removeEventListener("visibilitychange", onVisibilityChange);
349
- canvas.removeEventListener("mousemove", onMouseMove);
350
- canvas.removeEventListener("mouseleave", onMouseLeave);
351
- window.removeEventListener("scroll", onScroll);
352
- };
353
- }, [
354
- attractRadius,
355
- attractStrength,
356
- enabled,
357
- interactive,
358
- maxLinkDistance,
359
- resolvedPalette,
360
- particleCount,
361
- particleDensity,
362
- prefersReduced,
363
- resolvedTheme,
364
- velocity,
365
- ]);
366
-
367
- if (!enabled || prefersReduced) return null;
368
-
369
- return (
370
- <div
371
- className={cn("pointer-events-none absolute inset-0 overflow-hidden", className)}
372
- aria-hidden="true"
373
- {...props}
374
- >
375
- <canvas ref={canvasRef} className="absolute inset-0 h-full w-full" />
376
- </div>
377
- );
378
- }
@@ -1,91 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { ScrollArea } from "./scroll-area";
3
-
4
- const meta = {
5
- title: "Layout/ScrollArea",
6
- component: ScrollArea,
7
- tags: ["autodocs"],
8
- } satisfies Meta<typeof ScrollArea>;
9
-
10
- export default meta;
11
- type Story = StoryObj<typeof meta>;
12
-
13
- const placeholderItems = Array.from({ length: 8 }, (_, i) => ({
14
- id: i + 1,
15
- label: `Item ${i + 1}`,
16
- bg: `hsl(${i * 45}, 70%, 80%)`,
17
- }));
18
-
19
- export const Default: Story = {
20
- args: {
21
- className: "gap-4 p-4 max-w-lg",
22
- children: placeholderItems.map((item) => (
23
- <div
24
- key={item.id}
25
- className="flex size-40 shrink-0 items-center justify-center rounded-lg"
26
- style={{ backgroundColor: item.bg }}
27
- >
28
- <span className="font-medium" style={{ color: "#171714" }}>
29
- {item.label}
30
- </span>
31
- </div>
32
- )),
33
- },
34
- };
35
-
36
- export const WithSnap: Story = {
37
- args: {
38
- className: "gap-4 p-4 max-w-lg",
39
- snap: "start",
40
- children: placeholderItems.map((item) => (
41
- <div
42
- key={item.id}
43
- className="flex size-48 shrink-0 items-center justify-center rounded-lg"
44
- style={{ backgroundColor: item.bg }}
45
- >
46
- <span className="font-medium" style={{ color: "#171714" }}>
47
- {item.label}
48
- </span>
49
- </div>
50
- )),
51
- },
52
- };
53
-
54
- export const Vertical: Story = {
55
- args: {
56
- orientation: "vertical",
57
- className: "gap-3 p-4 max-h-64",
58
- children: placeholderItems.map((item) => (
59
- <div
60
- key={item.id}
61
- className="flex h-24 shrink-0 items-center justify-center rounded-lg"
62
- style={{ backgroundColor: item.bg }}
63
- >
64
- <span className="font-medium" style={{ color: "#171714" }}>
65
- {item.label}
66
- </span>
67
- </div>
68
- )),
69
- },
70
- };
71
-
72
- export const ScreenshotsGallery: Story = {
73
- render: () => (
74
- <div className="flex max-w-2xl flex-col gap-3">
75
- <h2 className="text-lg font-semibold">Screenshots</h2>
76
- <ScrollArea className="gap-4" snap="start">
77
- {placeholderItems.map((item) => (
78
- <div
79
- key={item.id}
80
- className="flex aspect-video w-72 shrink-0 items-center justify-center overflow-hidden rounded-xl border"
81
- style={{ backgroundColor: item.bg }}
82
- >
83
- <span className="text-sm" style={{ color: "#171714" }}>
84
- {item.label}
85
- </span>
86
- </div>
87
- ))}
88
- </ScrollArea>
89
- </div>
90
- ),
91
- };
@@ -1,79 +0,0 @@
1
- import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
2
- import * as React from "react";
3
-
4
- import { cn } from "../../lib/utils";
5
-
6
- interface ScrollAreaProps extends React.ComponentProps<typeof ScrollAreaPrimitive.Root> {
7
- orientation?: "horizontal" | "vertical";
8
- snap?: "start" | "center" | "none";
9
- }
10
-
11
- /**
12
- * Full literal class names, not `snap-${snap}` — Tailwind's scanner only
13
- * picks up complete strings present in the source, so an interpolated class
14
- * name never generates any CSS.
15
- */
16
- const SNAP_ALIGN_CLASS_NAME = {
17
- start: "snap-start",
18
- center: "snap-center",
19
- none: undefined,
20
- } as const satisfies Record<NonNullable<ScrollAreaProps["snap"]>, string | undefined>;
21
-
22
- /**
23
- * Overlay scrollbar: invisible at rest, fades in on hover/scroll and back
24
- * out on idle (data-hovering / data-scrolling from Base UI). Opacity-only,
25
- * transitions (not keyframes) since hover/scroll toggle rapidly.
26
- */
27
- const scrollbarClassName = cn(
28
- "flex touch-none p-0.5 opacity-0 transition-opacity duration-150 ease-out",
29
- "data-[hovering]:opacity-100 data-[scrolling]:opacity-100",
30
- "data-[orientation=vertical]:w-2.5",
31
- "data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col",
32
- );
33
-
34
- const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
35
- ({ className, children, orientation = "horizontal", snap = "none", ...props }, ref) => {
36
- return (
37
- <ScrollAreaPrimitive.Root
38
- ref={ref}
39
- className={cn(
40
- "relative flex overflow-hidden",
41
- orientation === "horizontal" ? "flex-row" : "flex-col",
42
- className,
43
- )}
44
- {...props}
45
- >
46
- <ScrollAreaPrimitive.Viewport
47
- className={cn(
48
- "scrollbar-hide flex overflow-auto",
49
- orientation === "horizontal" ? "min-w-0 flex-1 flex-row" : "min-h-0 flex-1 flex-col",
50
- snap !== "none" && (orientation === "horizontal" ? "snap-x" : "snap-y"),
51
- snap !== "none" && "snap-mandatory",
52
- )}
53
- >
54
- {React.Children.map(children, (child) => {
55
- if (!React.isValidElement(child)) return child;
56
- return React.cloneElement(child as React.ReactElement<{ className?: string }>, {
57
- className: cn(
58
- SNAP_ALIGN_CLASS_NAME[snap],
59
- (child.props as { className?: string }).className,
60
- ),
61
- });
62
- })}
63
- </ScrollAreaPrimitive.Viewport>
64
- <ScrollAreaPrimitive.Scrollbar orientation="horizontal" className={scrollbarClassName}>
65
- <ScrollAreaPrimitive.Thumb className="bg-foreground/40 hover:bg-foreground/60 relative flex-1 rounded-full transition-colors duration-150 ease" />
66
- </ScrollAreaPrimitive.Scrollbar>
67
- <ScrollAreaPrimitive.Scrollbar orientation="vertical" className={scrollbarClassName}>
68
- <ScrollAreaPrimitive.Thumb className="bg-foreground/40 hover:bg-foreground/60 relative flex-1 rounded-full transition-colors duration-150 ease" />
69
- </ScrollAreaPrimitive.Scrollbar>
70
- <ScrollAreaPrimitive.Corner />
71
- </ScrollAreaPrimitive.Root>
72
- );
73
- },
74
- );
75
-
76
- ScrollArea.displayName = "ScrollArea";
77
-
78
- export { ScrollArea };
79
- export type { ScrollAreaProps };
@@ -1,29 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import { Button } from "../primitives/button";
4
- import { SectionIntro } from "./section-intro";
5
-
6
- const meta = {
7
- title: "Layout/SectionIntro",
8
- component: SectionIntro,
9
- tags: ["autodocs"],
10
- } satisfies Meta<typeof SectionIntro>;
11
-
12
- export default meta;
13
- type Story = StoryObj<typeof meta>;
14
-
15
- export const Default: Story = {
16
- args: {
17
- title: "Accounts",
18
- description: "Connect and manage the accounts you track.",
19
- },
20
- };
21
-
22
- export const WithEyebrowAndActions: Story = {
23
- args: {
24
- eyebrow: "Settings",
25
- title: "Security",
26
- description: "Manage how you sign in and keep your account secure.",
27
- actions: <Button variant="outline">Add passkey</Button>,
28
- },
29
- };
@@ -1,31 +0,0 @@
1
- import type { ReactNode } from "react";
2
-
3
- export interface SectionIntroProps {
4
- eyebrow?: ReactNode;
5
- title: ReactNode;
6
- description?: ReactNode;
7
- actions?: ReactNode;
8
- }
9
-
10
- /**
11
- * Page-level heading composition: eyebrow label, title, description, and an
12
- * optional trailing actions slot.
13
- */
14
- export function SectionIntro({ eyebrow, title, description, actions }: SectionIntroProps) {
15
- return (
16
- <div className="flex items-start justify-between gap-6">
17
- <div className="min-w-0 flex-1">
18
- {eyebrow ? (
19
- <div className="text-muted-foreground mb-3 text-[11px] font-medium tracking-[0.18em] uppercase">
20
- {eyebrow}
21
- </div>
22
- ) : null}
23
- <div className="space-y-3">
24
- <h1 className="text-2xl text-foreground font-semibold tracking-tight">{title}</h1>
25
- {description ? <p className="text-muted-foreground text-sm">{description}</p> : null}
26
- </div>
27
- </div>
28
- {actions ? <div className="shrink-0">{actions}</div> : null}
29
- </div>
30
- );
31
- }
@@ -1,32 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import { SurfacePanel } from "./surface-panel";
4
-
5
- const meta = {
6
- title: "Layout/SurfacePanel",
7
- component: SurfacePanel,
8
- tags: ["autodocs"],
9
- } satisfies Meta<typeof SurfacePanel>;
10
-
11
- export default meta;
12
- type Story = StoryObj<typeof meta>;
13
-
14
- export const Default: Story = {
15
- render: () => (
16
- <div className="w-80">
17
- <SurfacePanel>
18
- <p className="text-muted-foreground text-sm">Panel content goes here.</p>
19
- </SurfacePanel>
20
- </div>
21
- ),
22
- };
23
-
24
- export const AsSection: Story = {
25
- render: () => (
26
- <div className="w-80">
27
- <SurfacePanel as="section" aria-label="Example panel">
28
- <p className="text-muted-foreground text-sm">Rendered as a semantic &lt;section&gt;.</p>
29
- </SurfacePanel>
30
- </div>
31
- ),
32
- };
@@ -1,23 +0,0 @@
1
- import type { ComponentPropsWithoutRef, ElementType } from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
-
5
- type SurfacePanelProps<T extends ElementType = "div"> = { as?: T } & Omit<
6
- ComponentPropsWithoutRef<T>,
7
- "as"
8
- >;
9
-
10
- /** Rounded, bordered surface panel — a generic content container. */
11
- export function SurfacePanel<T extends ElementType = "div">({
12
- as,
13
- className,
14
- ...props
15
- }: SurfacePanelProps<T>) {
16
- const Component = as ?? "div";
17
- return (
18
- <Component
19
- className={cn("border-border bg-card rounded-3xl border p-5", className)}
20
- {...props}
21
- />
22
- );
23
- }