agentic-ds-kit 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.
Files changed (345) hide show
  1. package/AGENTS.md +10 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +21 -0
  4. package/README.md +38 -0
  5. package/contracts/accordion.json +38 -0
  6. package/contracts/alert.json +57 -0
  7. package/contracts/alertdialog.json +89 -0
  8. package/contracts/appheader.json +140 -0
  9. package/contracts/appnav.json +72 -0
  10. package/contracts/avatar.json +63 -0
  11. package/contracts/avatargroup.json +83 -0
  12. package/contracts/badge.json +92 -0
  13. package/contracts/barchart.json +55 -0
  14. package/contracts/breadcrumb.json +76 -0
  15. package/contracts/button.json +120 -0
  16. package/contracts/buttongroup.json +48 -0
  17. package/contracts/calendar.json +93 -0
  18. package/contracts/card.json +123 -0
  19. package/contracts/carousel.json +98 -0
  20. package/contracts/cell.json +203 -0
  21. package/contracts/chat.json +131 -0
  22. package/contracts/checkbox.json +101 -0
  23. package/contracts/collapsible.json +66 -0
  24. package/contracts/command.json +84 -0
  25. package/contracts/datatable.json +126 -0
  26. package/contracts/datepicker.json +102 -0
  27. package/contracts/drawer.json +93 -0
  28. package/contracts/dropdownmenu.json +190 -0
  29. package/contracts/empty.json +79 -0
  30. package/contracts/field.json +49 -0
  31. package/contracts/fieldset.json +29 -0
  32. package/contracts/headercell.json +107 -0
  33. package/contracts/index.json +478 -0
  34. package/contracts/input.json +119 -0
  35. package/contracts/inputotp.json +119 -0
  36. package/contracts/insightcard.json +130 -0
  37. package/contracts/linechart.json +51 -0
  38. package/contracts/loadinganimation.json +33 -0
  39. package/contracts/modal.json +100 -0
  40. package/contracts/modalcard.json +88 -0
  41. package/contracts/navigationmenu.json +65 -0
  42. package/contracts/numbertransition.json +33 -0
  43. package/contracts/pageheader.json +101 -0
  44. package/contracts/pagination.json +85 -0
  45. package/contracts/patterns/activity.json +30 -0
  46. package/contracts/patterns/assistant-workspace.json +16 -0
  47. package/contracts/patterns/dashboard.json +12 -0
  48. package/contracts/patterns/empty-first-run.json +15 -0
  49. package/contracts/patterns/inbox.json +32 -0
  50. package/contracts/patterns/invite-members.json +14 -0
  51. package/contracts/patterns/list-detail.json +13 -0
  52. package/contracts/patterns/settings-form.json +13 -0
  53. package/contracts/piechart.json +68 -0
  54. package/contracts/progress.json +124 -0
  55. package/contracts/progresssteps.json +91 -0
  56. package/contracts/radiogroup.json +132 -0
  57. package/contracts/scoreboard.json +46 -0
  58. package/contracts/scorecard.json +124 -0
  59. package/contracts/section.json +97 -0
  60. package/contracts/select.json +130 -0
  61. package/contracts/shimmertext.json +33 -0
  62. package/contracts/sidenav.json +133 -0
  63. package/contracts/slider.json +81 -0
  64. package/contracts/spinner.json +52 -0
  65. package/contracts/switch.json +94 -0
  66. package/contracts/table.json +92 -0
  67. package/contracts/tabs.json +90 -0
  68. package/contracts/textarea.json +54 -0
  69. package/contracts/thinkinganimation.json +32 -0
  70. package/contracts/timeline.json +49 -0
  71. package/contracts/toast.json +104 -0
  72. package/contracts/tooltip.json +66 -0
  73. package/lib/tokens.css +637 -0
  74. package/package.json +60 -0
  75. package/skills/prototype-from-kit/SKILL.md +29 -0
  76. package/src/index.ts +71 -0
  77. package/tokens/tokens.json +725 -0
  78. package/ui/Accordion/Accordion.module.css +68 -0
  79. package/ui/Accordion/Accordion.tsx +51 -0
  80. package/ui/Accordion/Accordion.types.ts +10 -0
  81. package/ui/Accordion/index.ts +2 -0
  82. package/ui/Alert/Alert.module.css +62 -0
  83. package/ui/Alert/Alert.tsx +27 -0
  84. package/ui/Alert/Alert.types.ts +7 -0
  85. package/ui/Alert/index.ts +2 -0
  86. package/ui/AlertDialog/AlertDialog.module.css +71 -0
  87. package/ui/AlertDialog/AlertDialog.tsx +109 -0
  88. package/ui/AlertDialog/AlertDialog.types.ts +14 -0
  89. package/ui/AlertDialog/index.ts +6 -0
  90. package/ui/AppHeader/AppHeader.module.css +120 -0
  91. package/ui/AppHeader/AppHeader.tsx +70 -0
  92. package/ui/AppHeader/AppHeader.types.ts +28 -0
  93. package/ui/AppHeader/index.ts +2 -0
  94. package/ui/AppNav/AppNav.module.css +168 -0
  95. package/ui/AppNav/AppNav.tsx +137 -0
  96. package/ui/AppNav/AppNav.types.ts +24 -0
  97. package/ui/AppNav/index.ts +2 -0
  98. package/ui/Avatar/Avatar.module.css +52 -0
  99. package/ui/Avatar/Avatar.tsx +62 -0
  100. package/ui/Avatar/Avatar.types.ts +8 -0
  101. package/ui/Avatar/index.ts +2 -0
  102. package/ui/AvatarGroup/AvatarGroup.module.css +49 -0
  103. package/ui/AvatarGroup/AvatarGroup.tsx +29 -0
  104. package/ui/AvatarGroup/AvatarGroup.types.ts +16 -0
  105. package/ui/AvatarGroup/index.ts +2 -0
  106. package/ui/Badge/Badge.module.css +92 -0
  107. package/ui/Badge/Badge.tsx +43 -0
  108. package/ui/Badge/Badge.types.ts +11 -0
  109. package/ui/Badge/index.ts +2 -0
  110. package/ui/BarChart/BarChart.module.css +56 -0
  111. package/ui/BarChart/BarChart.tsx +380 -0
  112. package/ui/BarChart/BarChart.types.ts +18 -0
  113. package/ui/BarChart/index.ts +2 -0
  114. package/ui/Breadcrumb/Breadcrumb.module.css +64 -0
  115. package/ui/Breadcrumb/Breadcrumb.tsx +89 -0
  116. package/ui/Breadcrumb/Breadcrumb.types.ts +14 -0
  117. package/ui/Breadcrumb/index.ts +2 -0
  118. package/ui/Button/Button.module.css +101 -0
  119. package/ui/Button/Button.tsx +49 -0
  120. package/ui/Button/Button.types.ts +18 -0
  121. package/ui/Button/index.ts +2 -0
  122. package/ui/Button/lucideName.tsx +188 -0
  123. package/ui/ButtonGroup/ButtonGroup.module.css +31 -0
  124. package/ui/ButtonGroup/ButtonGroup.tsx +12 -0
  125. package/ui/ButtonGroup/ButtonGroup.types.ts +6 -0
  126. package/ui/ButtonGroup/index.ts +2 -0
  127. package/ui/Calendar/Calendar.module.css +146 -0
  128. package/ui/Calendar/Calendar.tsx +296 -0
  129. package/ui/Calendar/Calendar.types.ts +11 -0
  130. package/ui/Calendar/index.ts +2 -0
  131. package/ui/Card/Card.module.css +95 -0
  132. package/ui/Card/Card.tsx +69 -0
  133. package/ui/Card/Card.types.ts +23 -0
  134. package/ui/Card/index.ts +2 -0
  135. package/ui/Carousel/Carousel.module.css +114 -0
  136. package/ui/Carousel/Carousel.tsx +152 -0
  137. package/ui/Carousel/Carousel.types.ts +17 -0
  138. package/ui/Carousel/index.ts +7 -0
  139. package/ui/Cell/Cell.module.css +133 -0
  140. package/ui/Cell/Cell.tsx +300 -0
  141. package/ui/Cell/Cell.types.ts +69 -0
  142. package/ui/Cell/index.ts +11 -0
  143. package/ui/Chat/Chat.module.css +149 -0
  144. package/ui/Chat/Chat.tsx +175 -0
  145. package/ui/Chat/Chat.types.ts +39 -0
  146. package/ui/Chat/index.ts +2 -0
  147. package/ui/Checkbox/Checkbox.module.css +135 -0
  148. package/ui/Checkbox/Checkbox.tsx +70 -0
  149. package/ui/Checkbox/Checkbox.types.ts +15 -0
  150. package/ui/Checkbox/index.ts +2 -0
  151. package/ui/Collapsible/Collapsible.module.css +68 -0
  152. package/ui/Collapsible/Collapsible.tsx +59 -0
  153. package/ui/Collapsible/Collapsible.types.ts +9 -0
  154. package/ui/Collapsible/index.ts +2 -0
  155. package/ui/Command/Command.module.css +138 -0
  156. package/ui/Command/Command.tsx +152 -0
  157. package/ui/Command/Command.types.ts +20 -0
  158. package/ui/Command/index.ts +2 -0
  159. package/ui/DataTable/DataTable.module.css +211 -0
  160. package/ui/DataTable/DataTable.tsx +429 -0
  161. package/ui/DataTable/DataTable.types.ts +35 -0
  162. package/ui/DataTable/index.ts +8 -0
  163. package/ui/DatePicker/DatePicker.module.css +44 -0
  164. package/ui/DatePicker/DatePicker.tsx +123 -0
  165. package/ui/DatePicker/DatePicker.types.ts +15 -0
  166. package/ui/DatePicker/index.ts +2 -0
  167. package/ui/Drawer/Drawer.module.css +204 -0
  168. package/ui/Drawer/Drawer.tsx +112 -0
  169. package/ui/Drawer/Drawer.types.ts +14 -0
  170. package/ui/Drawer/index.ts +2 -0
  171. package/ui/DropdownMenu/DropdownMenu.module.css +319 -0
  172. package/ui/DropdownMenu/DropdownMenu.tsx +580 -0
  173. package/ui/DropdownMenu/DropdownMenu.types.ts +48 -0
  174. package/ui/DropdownMenu/index.ts +8 -0
  175. package/ui/Empty/Empty.module.css +68 -0
  176. package/ui/Empty/Empty.tsx +35 -0
  177. package/ui/Empty/Empty.types.ts +10 -0
  178. package/ui/Empty/index.ts +2 -0
  179. package/ui/Field/Field.module.css +66 -0
  180. package/ui/Field/Field.tsx +46 -0
  181. package/ui/Field/Field.types.ts +12 -0
  182. package/ui/Field/index.ts +2 -0
  183. package/ui/FieldSet/FieldSet.module.css +30 -0
  184. package/ui/FieldSet/FieldSet.tsx +14 -0
  185. package/ui/FieldSet/FieldSet.types.ts +7 -0
  186. package/ui/FieldSet/index.ts +2 -0
  187. package/ui/HeaderCell/HeaderCell.module.css +85 -0
  188. package/ui/HeaderCell/HeaderCell.tsx +71 -0
  189. package/ui/HeaderCell/HeaderCell.types.ts +18 -0
  190. package/ui/HeaderCell/index.ts +2 -0
  191. package/ui/Input/Input.module.css +59 -0
  192. package/ui/Input/Input.tsx +65 -0
  193. package/ui/Input/Input.types.ts +22 -0
  194. package/ui/Input/index.ts +2 -0
  195. package/ui/InputOTP/InputOTP.module.css +139 -0
  196. package/ui/InputOTP/InputOTP.tsx +136 -0
  197. package/ui/InputOTP/InputOTP.types.ts +19 -0
  198. package/ui/InputOTP/index.ts +2 -0
  199. package/ui/InsightCard/InsightCard.module.css +166 -0
  200. package/ui/InsightCard/InsightCard.tsx +90 -0
  201. package/ui/InsightCard/InsightCard.types.ts +20 -0
  202. package/ui/InsightCard/index.ts +7 -0
  203. package/ui/LineChart/LineChart.module.css +93 -0
  204. package/ui/LineChart/LineChart.tsx +424 -0
  205. package/ui/LineChart/LineChart.types.ts +19 -0
  206. package/ui/LineChart/index.ts +2 -0
  207. package/ui/LoadingAnimation/LoadingAnimation.module.css +66 -0
  208. package/ui/LoadingAnimation/LoadingAnimation.tsx +41 -0
  209. package/ui/LoadingAnimation/LoadingAnimation.types.ts +9 -0
  210. package/ui/LoadingAnimation/index.ts +6 -0
  211. package/ui/Modal/Modal.module.css +48 -0
  212. package/ui/Modal/Modal.tsx +114 -0
  213. package/ui/Modal/Modal.types.ts +15 -0
  214. package/ui/Modal/index.ts +2 -0
  215. package/ui/ModalCard/ModalCard.module.css +137 -0
  216. package/ui/ModalCard/ModalCard.tsx +58 -0
  217. package/ui/ModalCard/ModalCard.types.ts +15 -0
  218. package/ui/ModalCard/index.ts +2 -0
  219. package/ui/NavigationMenu/NavigationMenu.module.css +31 -0
  220. package/ui/NavigationMenu/NavigationMenu.tsx +57 -0
  221. package/ui/NavigationMenu/NavigationMenu.types.ts +22 -0
  222. package/ui/NavigationMenu/index.ts +7 -0
  223. package/ui/NumberTransition/NumberTransition.module.css +110 -0
  224. package/ui/NumberTransition/NumberTransition.tsx +78 -0
  225. package/ui/NumberTransition/NumberTransition.types.ts +9 -0
  226. package/ui/NumberTransition/index.ts +2 -0
  227. package/ui/PageHeader/PageHeader.module.css +57 -0
  228. package/ui/PageHeader/PageHeader.tsx +39 -0
  229. package/ui/PageHeader/PageHeader.types.ts +12 -0
  230. package/ui/PageHeader/index.ts +2 -0
  231. package/ui/Pagination/Pagination.module.css +19 -0
  232. package/ui/Pagination/Pagination.tsx +147 -0
  233. package/ui/Pagination/Pagination.types.ts +12 -0
  234. package/ui/Pagination/index.ts +2 -0
  235. package/ui/PieChart/PieChart.module.css +116 -0
  236. package/ui/PieChart/PieChart.tsx +293 -0
  237. package/ui/PieChart/PieChart.types.ts +22 -0
  238. package/ui/PieChart/index.ts +2 -0
  239. package/ui/Progress/Progress.module.css +226 -0
  240. package/ui/Progress/Progress.tsx +167 -0
  241. package/ui/Progress/Progress.types.ts +15 -0
  242. package/ui/Progress/index.ts +8 -0
  243. package/ui/ProgressSteps/ProgressSteps.module.css +210 -0
  244. package/ui/ProgressSteps/ProgressSteps.tsx +105 -0
  245. package/ui/ProgressSteps/ProgressSteps.types.ts +19 -0
  246. package/ui/ProgressSteps/index.ts +8 -0
  247. package/ui/RadioGroup/RadioGroup.module.css +284 -0
  248. package/ui/RadioGroup/RadioGroup.tsx +116 -0
  249. package/ui/RadioGroup/RadioGroup.types.ts +30 -0
  250. package/ui/RadioGroup/index.ts +8 -0
  251. package/ui/Scoreboard/Scoreboard.module.css +53 -0
  252. package/ui/Scoreboard/Scoreboard.tsx +41 -0
  253. package/ui/Scoreboard/Scoreboard.types.ts +7 -0
  254. package/ui/Scoreboard/index.ts +2 -0
  255. package/ui/Scorecard/Scorecard.module.css +150 -0
  256. package/ui/Scorecard/Scorecard.tsx +92 -0
  257. package/ui/Scorecard/Scorecard.types.ts +18 -0
  258. package/ui/Scorecard/index.ts +2 -0
  259. package/ui/Section/Section.module.css +104 -0
  260. package/ui/Section/Section.tsx +75 -0
  261. package/ui/Section/Section.types.ts +15 -0
  262. package/ui/Section/index.ts +2 -0
  263. package/ui/Select/Select.module.css +7 -0
  264. package/ui/Select/Select.tsx +113 -0
  265. package/ui/Select/Select.types.ts +26 -0
  266. package/ui/Select/index.ts +2 -0
  267. package/ui/ShimmerText/ShimmerText.module.css +46 -0
  268. package/ui/ShimmerText/ShimmerText.tsx +48 -0
  269. package/ui/ShimmerText/ShimmerText.types.ts +12 -0
  270. package/ui/ShimmerText/index.ts +2 -0
  271. package/ui/SideNav/SideNav.module.css +347 -0
  272. package/ui/SideNav/SideNav.tsx +222 -0
  273. package/ui/SideNav/SideNav.types.ts +42 -0
  274. package/ui/SideNav/index.ts +8 -0
  275. package/ui/Slider/Slider.module.css +105 -0
  276. package/ui/Slider/Slider.tsx +187 -0
  277. package/ui/Slider/Slider.types.ts +11 -0
  278. package/ui/Slider/index.ts +2 -0
  279. package/ui/Spinner/Spinner.module.css +58 -0
  280. package/ui/Spinner/Spinner.tsx +19 -0
  281. package/ui/Spinner/Spinner.types.ts +6 -0
  282. package/ui/Spinner/index.ts +2 -0
  283. package/ui/Switch/Switch.module.css +189 -0
  284. package/ui/Switch/Switch.tsx +65 -0
  285. package/ui/Switch/Switch.types.ts +12 -0
  286. package/ui/Switch/index.ts +2 -0
  287. package/ui/Table/Table.module.css +132 -0
  288. package/ui/Table/Table.tsx +108 -0
  289. package/ui/Table/Table.types.ts +26 -0
  290. package/ui/Table/index.ts +8 -0
  291. package/ui/Tabs/Tabs.module.css +162 -0
  292. package/ui/Tabs/Tabs.tsx +150 -0
  293. package/ui/Tabs/Tabs.types.ts +21 -0
  294. package/ui/Tabs/index.ts +2 -0
  295. package/ui/Textarea/Textarea.module.css +74 -0
  296. package/ui/Textarea/Textarea.tsx +38 -0
  297. package/ui/Textarea/Textarea.types.ts +14 -0
  298. package/ui/Textarea/index.ts +2 -0
  299. package/ui/ThinkingAnimation/ThinkingAnimation.module.css +75 -0
  300. package/ui/ThinkingAnimation/ThinkingAnimation.tsx +31 -0
  301. package/ui/ThinkingAnimation/ThinkingAnimation.types.ts +8 -0
  302. package/ui/ThinkingAnimation/index.ts +2 -0
  303. package/ui/Timeline/Timeline.module.css +99 -0
  304. package/ui/Timeline/Timeline.tsx +47 -0
  305. package/ui/Timeline/Timeline.types.ts +16 -0
  306. package/ui/Timeline/index.ts +2 -0
  307. package/ui/Toast/Toast.module.css +133 -0
  308. package/ui/Toast/Toast.tsx +89 -0
  309. package/ui/Toast/Toast.types.ts +17 -0
  310. package/ui/Toast/index.ts +2 -0
  311. package/ui/Tooltip/Tooltip.module.css +111 -0
  312. package/ui/Tooltip/Tooltip.tsx +101 -0
  313. package/ui/Tooltip/index.ts +2 -0
  314. package/ui/patterns/ActivityPattern/ActivityFeed.tsx +77 -0
  315. package/ui/patterns/ActivityPattern/ActivityPattern.tsx +120 -0
  316. package/ui/patterns/ActivityPattern/ActivityTasks.tsx +86 -0
  317. package/ui/patterns/ActivityPattern/TaskDrawer.module.css +39 -0
  318. package/ui/patterns/ActivityPattern/TaskDrawer.tsx +186 -0
  319. package/ui/patterns/ActivityPattern/index.ts +2 -0
  320. package/ui/patterns/AssistantWorkspacePattern/AssistantWorkspacePattern.module.css +16 -0
  321. package/ui/patterns/AssistantWorkspacePattern/AssistantWorkspacePattern.tsx +159 -0
  322. package/ui/patterns/AssistantWorkspacePattern/index.ts +2 -0
  323. package/ui/patterns/DashboardPattern/DashboardPattern.tsx +102 -0
  324. package/ui/patterns/DashboardPattern/index.ts +1 -0
  325. package/ui/patterns/DashboardPattern/patterns.module.css +6 -0
  326. package/ui/patterns/EmptyFirstRunPattern/EmptyFirstRunPattern.tsx +125 -0
  327. package/ui/patterns/EmptyFirstRunPattern/index.ts +2 -0
  328. package/ui/patterns/InboxPattern/InboxPattern.module.css +40 -0
  329. package/ui/patterns/InboxPattern/InboxPattern.tsx +192 -0
  330. package/ui/patterns/InboxPattern/InboxReplyDrawer.tsx +97 -0
  331. package/ui/patterns/InboxPattern/inbox-data.ts +101 -0
  332. package/ui/patterns/InboxPattern/index.ts +2 -0
  333. package/ui/patterns/InviteMembersPattern/InviteMembersPattern.tsx +148 -0
  334. package/ui/patterns/InviteMembersPattern/index.ts +2 -0
  335. package/ui/patterns/ListDetailPattern/ListDetailPattern.module.css +34 -0
  336. package/ui/patterns/ListDetailPattern/ListDetailPattern.tsx +145 -0
  337. package/ui/patterns/ListDetailPattern/index.ts +2 -0
  338. package/ui/patterns/SettingsFormPattern/SettingsFormPattern.module.css +12 -0
  339. package/ui/patterns/SettingsFormPattern/SettingsFormPattern.tsx +89 -0
  340. package/ui/patterns/SettingsFormPattern/index.ts +3 -0
  341. package/ui/shared/ChartFrame.module.css +295 -0
  342. package/ui/shared/ChartFrame.tsx +87 -0
  343. package/ui/shared/chartMath.ts +228 -0
  344. package/ui/shared/controls.module.css +48 -0
  345. package/ui/shared/useChartSize.ts +22 -0
@@ -0,0 +1,57 @@
1
+ .header {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--space-3);
5
+ width: 100%;
6
+ }
7
+
8
+ .trail {
9
+ min-width: 0;
10
+ }
11
+
12
+ .row {
13
+ display: flex;
14
+ flex-wrap: wrap;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ gap: var(--space-4);
18
+ }
19
+
20
+ .content {
21
+ flex: 1;
22
+ min-width: 0;
23
+ }
24
+
25
+ .eyebrow {
26
+ margin: 0 0 var(--space-2);
27
+ font-family: var(--font-sans);
28
+ font-size: var(--type-size-label);
29
+ font-weight: var(--type-weight-label);
30
+ letter-spacing: 0.08em;
31
+ text-transform: uppercase;
32
+ color: var(--text-secondary);
33
+ }
34
+
35
+ .title {
36
+ margin: 0 0 var(--space-2);
37
+ font-family: var(--font-sans);
38
+ font-size: var(--type-size-title);
39
+ font-weight: var(--type-weight-title);
40
+ line-height: var(--type-line-heading);
41
+ color: var(--text-primary);
42
+ }
43
+
44
+ .subtitle {
45
+ margin: 0;
46
+ max-width: 36rem;
47
+ font-family: var(--font-sans);
48
+ font-size: var(--type-size-body);
49
+ line-height: var(--type-line-body);
50
+ color: var(--text-secondary);
51
+ }
52
+
53
+ .actions {
54
+ display: flex;
55
+ flex-wrap: wrap;
56
+ gap: var(--space-3);
57
+ }
@@ -0,0 +1,39 @@
1
+ import { Breadcrumb } from "../Breadcrumb";
2
+ import type { PageHeaderProps } from "./PageHeader.types";
3
+ import styles from "./PageHeader.module.css";
4
+
5
+ export type { PageHeaderProps } from "./PageHeader.types";
6
+
7
+ export function PageHeader({
8
+ title,
9
+ subtitle,
10
+ eyebrow,
11
+ actions,
12
+ breadcrumbs,
13
+ breadcrumbSeparator,
14
+ breadcrumbMaxItems,
15
+ }: PageHeaderProps) {
16
+ const trail = breadcrumbs && breadcrumbs.length >= 2 ? breadcrumbs : undefined;
17
+
18
+ return (
19
+ <header className={styles.header}>
20
+ {trail ? (
21
+ <div className={styles.trail}>
22
+ <Breadcrumb
23
+ items={trail}
24
+ separator={breadcrumbSeparator}
25
+ maxItems={breadcrumbMaxItems}
26
+ />
27
+ </div>
28
+ ) : null}
29
+ <div className={styles.row}>
30
+ <div className={styles.content}>
31
+ {eyebrow ? <p className={styles.eyebrow}>{eyebrow}</p> : null}
32
+ <h1 className={styles.title}>{title}</h1>
33
+ {subtitle ? <p className={styles.subtitle}>{subtitle}</p> : null}
34
+ </div>
35
+ {actions ? <div className={styles.actions}>{actions}</div> : null}
36
+ </div>
37
+ </header>
38
+ );
39
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ import type { BreadcrumbItem, BreadcrumbSeparator } from "../Breadcrumb";
3
+
4
+ export interface PageHeaderProps {
5
+ title: string;
6
+ subtitle?: string;
7
+ eyebrow?: string;
8
+ actions?: ReactNode;
9
+ breadcrumbs?: BreadcrumbItem[];
10
+ breadcrumbSeparator?: BreadcrumbSeparator;
11
+ breadcrumbMaxItems?: number;
12
+ }
@@ -0,0 +1,2 @@
1
+ export { PageHeader } from "./PageHeader";
2
+ export type { PageHeaderProps } from "./PageHeader.types";
@@ -0,0 +1,19 @@
1
+ .nav {
2
+ display: flex;
3
+ flex-direction: row;
4
+ align-items: center;
5
+ justify-content: center;
6
+ gap: var(--space-1);
7
+ }
8
+
9
+ .page {
10
+ display: inline-flex;
11
+ }
12
+
13
+ .ellipsis {
14
+ display: inline-flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ min-inline-size: var(--space-8);
18
+ color: var(--icon-muted);
19
+ }
@@ -0,0 +1,147 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Button } from "../Button";
5
+ import { LucideByName } from "../Button/lucideName";
6
+ import type { PaginationProps } from "./Pagination.types";
7
+ import styles from "./Pagination.module.css";
8
+
9
+ export type { PaginationProps, PaginationSize } from "./Pagination.types";
10
+
11
+ const ICON_SIZE = { sm: 14, md: 16 } as const;
12
+
13
+ function clampCount(value: number): number {
14
+ if (!Number.isFinite(value) || value < 1) return 1;
15
+ return Math.floor(value);
16
+ }
17
+
18
+ function clampPage(value: number, count: number): number {
19
+ if (!Number.isFinite(value)) return 1;
20
+ return Math.min(count, Math.max(1, Math.round(value)));
21
+ }
22
+
23
+ type WindowNode = { kind: "page"; page: number } | { kind: "ellipsis"; id: string };
24
+
25
+ function pageWindow(current: number, count: number): WindowNode[] {
26
+ if (count <= 7) {
27
+ return Array.from({ length: count }, (_, i) => ({ kind: "page" as const, page: i + 1 }));
28
+ }
29
+
30
+ const pages = new Set<number>([1, count, current]);
31
+ if (current - 1 >= 1) pages.add(current - 1);
32
+ if (current + 1 <= count) pages.add(current + 1);
33
+
34
+ const sorted = [...pages].sort((a, b) => a - b);
35
+ const nodes: WindowNode[] = [];
36
+ for (let i = 0; i < sorted.length; i += 1) {
37
+ const page = sorted[i];
38
+ if (i > 0 && page - sorted[i - 1] > 1) {
39
+ nodes.push({ kind: "ellipsis", id: `gap-${sorted[i - 1]}-${page}` });
40
+ }
41
+ nodes.push({ kind: "page", page });
42
+ }
43
+ return nodes;
44
+ }
45
+
46
+ export function Pagination({
47
+ page,
48
+ defaultPage = 1,
49
+ pageCount,
50
+ onPageChange,
51
+ showPages = true,
52
+ showLabels = true,
53
+ showPreviousNext = true,
54
+ size = "md",
55
+ }: PaginationProps) {
56
+ const count = clampCount(pageCount);
57
+ const isControlled = typeof page === "number";
58
+ const [uncontrolled, setUncontrolled] = useState(() => clampPage(defaultPage, count));
59
+ const current = clampPage(isControlled ? page : uncontrolled, count);
60
+ const iconPx = ICON_SIZE[size];
61
+
62
+ function go(next: number) {
63
+ const nextPage = clampPage(next, count);
64
+ if (nextPage === current) return;
65
+ if (!isControlled) setUncontrolled(nextPage);
66
+ onPageChange?.(nextPage);
67
+ }
68
+
69
+ return (
70
+ <nav aria-label="Pagination" className={styles.nav}>
71
+ {showPreviousNext ? (
72
+ showLabels ? (
73
+ <Button
74
+ size={size}
75
+ variant="tertiary"
76
+ iconStart="ChevronLeft"
77
+ disabled={current <= 1}
78
+ onClick={() => go(current - 1)}
79
+ >
80
+ Previous
81
+ </Button>
82
+ ) : (
83
+ <Button
84
+ size={size}
85
+ variant="tertiary"
86
+ iconStart="ChevronLeft"
87
+ ariaLabel="Previous"
88
+ disabled={current <= 1}
89
+ onClick={() => go(current - 1)}
90
+ />
91
+ )
92
+ ) : null}
93
+
94
+ {showPages
95
+ ? pageWindow(current, count).map((node) => {
96
+ if (node.kind === "ellipsis") {
97
+ return (
98
+ <span key={node.id} className={styles.ellipsis} aria-hidden>
99
+ <LucideByName name="Ellipsis" size={iconPx} />
100
+ </span>
101
+ );
102
+ }
103
+
104
+ const isCurrent = node.page === current;
105
+ return (
106
+ <span
107
+ key={node.page}
108
+ className={styles.page}
109
+ aria-current={isCurrent ? "page" : undefined}
110
+ >
111
+ <Button
112
+ size={size}
113
+ variant={isCurrent ? "secondary" : "tertiary"}
114
+ onClick={() => go(node.page)}
115
+ >
116
+ {String(node.page)}
117
+ </Button>
118
+ </span>
119
+ );
120
+ })
121
+ : null}
122
+
123
+ {showPreviousNext ? (
124
+ showLabels ? (
125
+ <Button
126
+ size={size}
127
+ variant="tertiary"
128
+ iconEnd="ChevronRight"
129
+ disabled={current >= count}
130
+ onClick={() => go(current + 1)}
131
+ >
132
+ Next
133
+ </Button>
134
+ ) : (
135
+ <Button
136
+ size={size}
137
+ variant="tertiary"
138
+ iconStart="ChevronRight"
139
+ ariaLabel="Next"
140
+ disabled={current >= count}
141
+ onClick={() => go(current + 1)}
142
+ />
143
+ )
144
+ ) : null}
145
+ </nav>
146
+ );
147
+ }
@@ -0,0 +1,12 @@
1
+ export type PaginationSize = "sm" | "md";
2
+
3
+ export interface PaginationProps {
4
+ page?: number;
5
+ defaultPage?: number;
6
+ pageCount: number;
7
+ onPageChange?: (page: number) => void;
8
+ showPages?: boolean;
9
+ showLabels?: boolean;
10
+ showPreviousNext?: boolean;
11
+ size?: PaginationSize;
12
+ }
@@ -0,0 +1,2 @@
1
+ export { Pagination } from "./Pagination";
2
+ export type { PaginationProps, PaginationSize } from "./Pagination.types";
@@ -0,0 +1,116 @@
1
+ .root {
2
+ container-type: inline-size;
3
+ overflow: visible;
4
+ }
5
+
6
+ .stackPlot {
7
+ display: grid;
8
+ place-items: center;
9
+ }
10
+
11
+ .split {
12
+ display: flex;
13
+ flex-direction: row;
14
+ align-items: center;
15
+ gap: var(--space-6);
16
+ width: fit-content;
17
+ margin-inline: auto;
18
+ }
19
+
20
+ .plot {
21
+ position: relative;
22
+ flex: none;
23
+ overflow: visible;
24
+ }
25
+
26
+ .chart {
27
+ display: block;
28
+ overflow: visible;
29
+ }
30
+
31
+ .slice {
32
+ transform-box: view-box;
33
+ transform-origin: center;
34
+ transition:
35
+ transform 0.56s cubic-bezier(0.22, 1, 0.36, 1),
36
+ opacity 0.56s cubic-bezier(0.22, 1, 0.36, 1),
37
+ filter 0.56s cubic-bezier(0.22, 1, 0.36, 1);
38
+ }
39
+
40
+ .sliceActive {
41
+ transform: scale(1.015);
42
+ }
43
+
44
+ .sliceDimmed {
45
+ opacity: 0.45;
46
+ filter: blur(1.5px);
47
+ }
48
+
49
+ .glow {
50
+ pointer-events: none;
51
+ opacity: 0;
52
+ transition: opacity 0.56s cubic-bezier(0.22, 1, 0.36, 1);
53
+ }
54
+
55
+ .sliceActive .glow {
56
+ opacity: 0.38;
57
+ }
58
+
59
+ .mark {
60
+ pointer-events: none;
61
+ }
62
+
63
+ .hit {
64
+ fill: transparent;
65
+ cursor: pointer;
66
+ outline: none;
67
+ }
68
+
69
+ .hit:focus-visible {
70
+ outline: var(--border-focus-width) solid var(--focus-ring);
71
+ outline-offset: var(--space-inset-2xs);
72
+ }
73
+
74
+ .center {
75
+ pointer-events: none;
76
+ }
77
+
78
+ .centerValue {
79
+ fill: var(--text-primary);
80
+ font-family: var(--font-sans);
81
+ font-weight: var(--type-weight-heading);
82
+ font-variant-numeric: tabular-nums;
83
+ }
84
+
85
+ .centerLabel {
86
+ fill: var(--text-muted);
87
+ font-family: var(--font-sans);
88
+ font-weight: var(--type-weight-caption);
89
+ }
90
+
91
+
92
+ @container (max-width: 30rem) {
93
+ .split {
94
+ flex-direction: column;
95
+ align-items: center;
96
+ }
97
+ }
98
+
99
+ @media (prefers-reduced-motion: reduce) {
100
+ .slice,
101
+ .glow {
102
+ transition: opacity var(--motion-interaction-duration) var(--motion-interaction-easing);
103
+ }
104
+
105
+ .sliceActive {
106
+ transform: none;
107
+ }
108
+
109
+ .sliceDimmed {
110
+ filter: none;
111
+ }
112
+
113
+ .sliceActive .glow {
114
+ opacity: 0;
115
+ }
116
+ }
@@ -0,0 +1,293 @@
1
+ "use client";
2
+
3
+ import { useId, useMemo, useState } from "react";
4
+ import { ChartFrame } from "../shared/ChartFrame";
5
+ import frameStyles from "../shared/ChartFrame.module.css";
6
+ import {
7
+ formatValue,
8
+ pointOnCircle,
9
+ prepareSegments,
10
+ ringPath,
11
+ TAU,
12
+ wedgePath,
13
+ type PreparedSegment,
14
+ } from "../shared/chartMath";
15
+ import type { PieChartProps } from "./PieChart.types";
16
+ import styles from "./PieChart.module.css";
17
+
18
+ export type { PieChartLayout, PieChartProps, PieChartVariant } from "./PieChart.types";
19
+
20
+ type SliceGeom = PreparedSegment & {
21
+ path: string;
22
+ midX: number;
23
+ midY: number;
24
+ percent: number;
25
+ };
26
+
27
+ function ChartTable({ segments, total }: { segments: PreparedSegment[]; total: number }) {
28
+ return (
29
+ <div className={frameStyles.tableScroll}>
30
+ <table className={frameStyles.table}>
31
+ <thead>
32
+ <tr>
33
+ <th scope="col">Segment</th>
34
+ <th scope="col">Value</th>
35
+ <th scope="col">Share</th>
36
+ </tr>
37
+ </thead>
38
+ <tbody>
39
+ {segments.map((segment) => (
40
+ <tr key={segment.label}>
41
+ <th scope="row">{segment.label}</th>
42
+ <td>{formatValue(segment.value)}</td>
43
+ <td>{`${Math.round(segment.share * 100)}%`}</td>
44
+ </tr>
45
+ ))}
46
+ <tr>
47
+ <th scope="row">Total</th>
48
+ <td>{formatValue(total)}</td>
49
+ <td>100%</td>
50
+ </tr>
51
+ </tbody>
52
+ </table>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ function Legend({
58
+ segments,
59
+ active,
60
+ orientation,
61
+ onActivate,
62
+ onClear,
63
+ }: {
64
+ segments: PreparedSegment[];
65
+ active: string | null;
66
+ orientation: "vertical" | "horizontal";
67
+ onActivate: (label: string) => void;
68
+ onClear: () => void;
69
+ }) {
70
+ return (
71
+ <ul
72
+ className={`${frameStyles.legend} ${frameStyles.legendInteractive}${orientation === "vertical" ? ` ${frameStyles.legendVertical}` : ""}`}
73
+ >
74
+ {segments.map((segment) => {
75
+ const dimmed = Boolean(active) && active !== segment.label;
76
+ const percent = `${Math.round(segment.share * 100)}%`;
77
+ return (
78
+ <li key={segment.label}>
79
+ <button
80
+ type="button"
81
+ className={`${frameStyles.legendItem} ${frameStyles.legendButton}${dimmed ? ` ${frameStyles.legendItemDimmed}` : ""}`}
82
+ onMouseEnter={() => onActivate(segment.label)}
83
+ onMouseLeave={onClear}
84
+ onFocus={() => onActivate(segment.label)}
85
+ onBlur={onClear}
86
+ >
87
+ <span
88
+ className={`${frameStyles.legendSwatch} ${frameStyles.legendSwatchPill}`}
89
+ style={{ background: segment.color }}
90
+ aria-hidden
91
+ />
92
+ <span>{segment.label}</span>
93
+ <span className={orientation === "vertical" ? frameStyles.legendPercent : frameStyles.legendMeta}>
94
+ {percent}
95
+ </span>
96
+ </button>
97
+ </li>
98
+ );
99
+ })}
100
+ </ul>
101
+ );
102
+ }
103
+
104
+ export function PieChart({
105
+ data = [],
106
+ title,
107
+ description,
108
+ variant = "donut",
109
+ layout = "split",
110
+ size = 240,
111
+ maxSegments = 6,
112
+ sort = true,
113
+ otherLabel = "Other",
114
+ centerLabel = "Total",
115
+ showCenterTotal = true,
116
+ showTable = true,
117
+ emptyLabel = "No data for this range",
118
+ isLoading = false,
119
+ className = "",
120
+ }: PieChartProps) {
121
+ const [active, setActive] = useState<string | null>(null);
122
+ const rawId = useId();
123
+ const glowId = `pie-glow-${rawId.replace(/:/g, "")}`;
124
+ const isDonut = variant === "donut";
125
+ const isSplit = layout === "split";
126
+
127
+ const segments = useMemo(
128
+ () => prepareSegments(data, { maxSegments, sort, otherLabel }),
129
+ [data, maxSegments, sort, otherLabel],
130
+ );
131
+
132
+ const total = useMemo(() => segments.reduce((sum, entry) => sum + entry.value, 0), [segments]);
133
+ const isEmpty = !segments.length || total <= 0;
134
+
135
+ const geometry = useMemo(() => {
136
+ const cx = size / 2;
137
+ const cy = size / 2;
138
+ const pad = 4;
139
+ const outerRadius = size / 2 - pad;
140
+ const thickness = isDonut ? outerRadius * 0.34 : 0;
141
+ const innerRadius = isDonut ? Math.max(0, outerRadius - thickness) : 0;
142
+ const midRadius = isDonut ? (innerRadius + outerRadius) / 2 : outerRadius * 0.55;
143
+
144
+ if (isEmpty) {
145
+ return { cx, cy, slices: [] as SliceGeom[] };
146
+ }
147
+
148
+ let cursor = 0;
149
+ const slices: SliceGeom[] = segments.map((segment) => {
150
+ const sweep = segment.share * TAU;
151
+ const start = cursor;
152
+ const end = cursor + sweep;
153
+ cursor = end;
154
+ const mid = (start + end) / 2;
155
+ const point = pointOnCircle(cx, cy, midRadius, mid);
156
+ return {
157
+ ...segment,
158
+ path: isDonut
159
+ ? ringPath({ cx, cy, innerRadius, outerRadius, startAngle: start, endAngle: end })
160
+ : wedgePath({ cx, cy, radius: outerRadius, startAngle: start, endAngle: end }),
161
+ midX: point.x,
162
+ midY: point.y,
163
+ percent: Math.round(segment.share * 100),
164
+ };
165
+ });
166
+
167
+ return { cx, cy, slices };
168
+ }, [isDonut, isEmpty, segments, size]);
169
+
170
+ const activeSlice = geometry.slices.find((slice) => slice.label === active) ?? null;
171
+ const compactTotal = formatValue(total);
172
+ const valueSize = size * 0.14;
173
+ const labelSize = size * 0.07;
174
+
175
+ const legend = !isEmpty ? (
176
+ <Legend
177
+ segments={segments}
178
+ active={active}
179
+ orientation={isSplit ? "vertical" : "horizontal"}
180
+ onActivate={setActive}
181
+ onClear={() => setActive(null)}
182
+ />
183
+ ) : undefined;
184
+
185
+ const plot = (
186
+ <div className={styles.plot} style={{ width: size, height: size }}>
187
+ <svg
188
+ className={styles.chart}
189
+ width={size}
190
+ height={size}
191
+ viewBox={`0 0 ${size} ${size}`}
192
+ overflow="visible"
193
+ >
194
+ <defs>
195
+ <filter id={glowId} x="-40%" y="-40%" width="180%" height="180%">
196
+ <feGaussianBlur stdDeviation="2.2" />
197
+ </filter>
198
+ </defs>
199
+ {geometry.slices.map((slice) => {
200
+ const isActive = active === slice.label;
201
+ const isDimmed = Boolean(active) && !isActive;
202
+ return (
203
+ <g
204
+ key={slice.label}
205
+ className={`${styles.slice}${isActive ? ` ${styles.sliceActive}` : ""}${isDimmed ? ` ${styles.sliceDimmed}` : ""}`}
206
+ >
207
+ <path
208
+ className={styles.glow}
209
+ d={slice.path}
210
+ fill={slice.color}
211
+ filter={`url(#${glowId})`}
212
+ />
213
+ <path className={styles.mark} d={slice.path} fill={slice.color} />
214
+ </g>
215
+ );
216
+ })}
217
+ {isDonut && showCenterTotal && !isEmpty ? (
218
+ <text className={styles.center} textAnchor="middle">
219
+ <tspan
220
+ className={styles.centerValue}
221
+ x={geometry.cx}
222
+ y={geometry.cy}
223
+ dy="-0.15em"
224
+ fontSize={valueSize}
225
+ >
226
+ {compactTotal}
227
+ </tspan>
228
+ <tspan
229
+ className={styles.centerLabel}
230
+ x={geometry.cx}
231
+ dy="1.45em"
232
+ fontSize={labelSize}
233
+ >
234
+ {centerLabel}
235
+ </tspan>
236
+ </text>
237
+ ) : null}
238
+ {geometry.slices.map((slice) => (
239
+ <path
240
+ key={`hit-${slice.label}`}
241
+ className={styles.hit}
242
+ d={slice.path}
243
+ fill="transparent"
244
+ tabIndex={0}
245
+ role="button"
246
+ aria-label={`${slice.label}: ${formatValue(slice.value)}, ${slice.percent} percent`}
247
+ onMouseEnter={() => setActive(slice.label)}
248
+ onMouseLeave={() => setActive(null)}
249
+ onFocus={() => setActive(slice.label)}
250
+ onBlur={() => setActive(null)}
251
+ />
252
+ ))}
253
+ </svg>
254
+ {activeSlice ? (
255
+ <div
256
+ className={`${frameStyles.tooltip}${activeSlice.midX > size * 0.6 ? ` ${frameStyles.tooltipFlip}` : ""}`}
257
+ style={{ left: activeSlice.midX, top: activeSlice.midY }}
258
+ role="status"
259
+ aria-live="polite"
260
+ >
261
+ <p className={frameStyles.tooltipTitle}>{activeSlice.label}</p>
262
+ <div className={frameStyles.tooltipRow}>
263
+ <span className={frameStyles.tooltipKey} style={{ background: activeSlice.color }} aria-hidden />
264
+ <span className={frameStyles.tooltipShare}>{`${activeSlice.percent}% of ${compactTotal}`}</span>
265
+ <span className={frameStyles.tooltipValue}>{formatValue(activeSlice.value)}</span>
266
+ </div>
267
+ </div>
268
+ ) : null}
269
+ </div>
270
+ );
271
+
272
+ return (
273
+ <ChartFrame
274
+ title={title}
275
+ description={description}
276
+ legend={!isSplit ? legend : undefined}
277
+ table={showTable ? <ChartTable segments={segments} total={total} /> : undefined}
278
+ isEmpty={isEmpty}
279
+ emptyLabel={emptyLabel}
280
+ isLoading={isLoading}
281
+ className={`pie-chart pie-chart--${layout} ${styles.root} ${className}`.trim()}
282
+ >
283
+ {isSplit ? (
284
+ <div className={styles.split}>
285
+ {plot}
286
+ {legend}
287
+ </div>
288
+ ) : (
289
+ <div className={styles.stackPlot}>{plot}</div>
290
+ )}
291
+ </ChartFrame>
292
+ );
293
+ }