@blurrow/ui 0.2.0 → 0.2.1

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 (271) hide show
  1. package/README.md +48 -36
  2. package/dist/Accordion.d.ts +17 -0
  3. package/dist/Accordion.js +138 -0
  4. package/dist/Accordion.js.map +1 -0
  5. package/dist/Alert.d.ts +13 -0
  6. package/dist/Alert.js +69 -0
  7. package/dist/Alert.js.map +1 -0
  8. package/dist/AuthorCard.d.ts +12 -0
  9. package/dist/AuthorCard.js +57 -0
  10. package/dist/AuthorCard.js.map +1 -0
  11. package/dist/Avatar.d.ts +21 -0
  12. package/dist/Avatar.js +96 -0
  13. package/dist/Avatar.js.map +1 -0
  14. package/dist/Badge.d.ts +12 -0
  15. package/dist/Badge.js +46 -0
  16. package/dist/Badge.js.map +1 -0
  17. package/dist/Banner.d.ts +15 -0
  18. package/dist/Banner.js +68 -0
  19. package/dist/Banner.js.map +1 -0
  20. package/dist/Breadcrumb.d.ts +13 -0
  21. package/dist/Breadcrumb.js +45 -0
  22. package/dist/Breadcrumb.js.map +1 -0
  23. package/dist/Button.d.ts +19 -0
  24. package/dist/Button.js +282 -0
  25. package/dist/Button.js.map +1 -0
  26. package/dist/ButtonGroup.d.ts +19 -0
  27. package/dist/ButtonGroup.js +62 -0
  28. package/dist/ButtonGroup.js.map +1 -0
  29. package/dist/Card.d.ts +40 -0
  30. package/dist/Card.js +175 -0
  31. package/dist/Card.js.map +1 -0
  32. package/dist/CardShell.d.ts +18 -0
  33. package/dist/CardShell.js +58 -0
  34. package/dist/CardShell.js.map +1 -0
  35. package/dist/Carousel.d.ts +22 -0
  36. package/dist/Carousel.js +247 -0
  37. package/dist/Carousel.js.map +1 -0
  38. package/dist/Checkbox.d.ts +15 -0
  39. package/dist/Checkbox.js +84 -0
  40. package/dist/Checkbox.js.map +1 -0
  41. package/dist/CloseButton.d.ts +12 -0
  42. package/dist/CloseButton.js +63 -0
  43. package/dist/CloseButton.js.map +1 -0
  44. package/dist/CodeBlock.d.ts +12 -0
  45. package/dist/CodeBlock.js +221 -0
  46. package/dist/CodeBlock.js.map +1 -0
  47. package/dist/ColorPicker.d.ts +24 -0
  48. package/dist/ColorPicker.js +366 -0
  49. package/dist/ColorPicker.js.map +1 -0
  50. package/dist/Combobox.d.ts +34 -0
  51. package/dist/Combobox.js +255 -0
  52. package/dist/Combobox.js.map +1 -0
  53. package/dist/CommandPalette.d.ts +25 -0
  54. package/dist/CommandPalette.js +304 -0
  55. package/dist/CommandPalette.js.map +1 -0
  56. package/dist/ContextMenu.d.ts +16 -0
  57. package/dist/ContextMenu.js +209 -0
  58. package/dist/ContextMenu.js.map +1 -0
  59. package/dist/DataList.d.ts +15 -0
  60. package/dist/DataList.js +42 -0
  61. package/dist/DataList.js.map +1 -0
  62. package/dist/DatePicker.d.ts +65 -0
  63. package/dist/DatePicker.js +391 -0
  64. package/dist/DatePicker.js.map +1 -0
  65. package/dist/DateRangePicker.d.ts +34 -0
  66. package/dist/DateRangePicker.js +180 -0
  67. package/dist/DateRangePicker.js.map +1 -0
  68. package/dist/Drawer.d.ts +17 -0
  69. package/dist/Drawer.js +132 -0
  70. package/dist/Drawer.js.map +1 -0
  71. package/dist/DropdownMenu.d.ts +23 -0
  72. package/dist/DropdownMenu.js +185 -0
  73. package/dist/DropdownMenu.js.map +1 -0
  74. package/dist/EmptyState.d.ts +13 -0
  75. package/dist/EmptyState.js +55 -0
  76. package/dist/EmptyState.js.map +1 -0
  77. package/dist/FieldMessage.d.ts +26 -0
  78. package/dist/FieldMessage.js +95 -0
  79. package/dist/FieldMessage.js.map +1 -0
  80. package/dist/FileUpload.d.ts +32 -0
  81. package/dist/FileUpload.js +253 -0
  82. package/dist/FileUpload.js.map +1 -0
  83. package/dist/Floating.d.ts +32 -0
  84. package/dist/Floating.js +95 -0
  85. package/dist/Floating.js.map +1 -0
  86. package/dist/HoverCard.d.ts +18 -0
  87. package/dist/HoverCard.js +90 -0
  88. package/dist/HoverCard.js.map +1 -0
  89. package/dist/Input.d.ts +17 -0
  90. package/dist/Input.js +96 -0
  91. package/dist/Input.js.map +1 -0
  92. package/dist/Kbd.d.ts +9 -0
  93. package/dist/Kbd.js +24 -0
  94. package/dist/Kbd.js.map +1 -0
  95. package/dist/Modal.d.ts +15 -0
  96. package/dist/Modal.js +109 -0
  97. package/dist/Modal.js.map +1 -0
  98. package/dist/NumberInput.d.ts +23 -0
  99. package/dist/NumberInput.js +162 -0
  100. package/dist/NumberInput.js.map +1 -0
  101. package/dist/OtpInput.d.ts +26 -0
  102. package/dist/OtpInput.js +173 -0
  103. package/dist/OtpInput.js.map +1 -0
  104. package/dist/Pagination.d.ts +11 -0
  105. package/dist/Pagination.js +148 -0
  106. package/dist/Pagination.js.map +1 -0
  107. package/dist/Popover.d.ts +13 -0
  108. package/dist/Popover.js +98 -0
  109. package/dist/Popover.js.map +1 -0
  110. package/dist/Progress.d.ts +12 -0
  111. package/dist/Progress.js +61 -0
  112. package/dist/Progress.js.map +1 -0
  113. package/dist/QuoteCard.d.ts +13 -0
  114. package/dist/QuoteCard.js +92 -0
  115. package/dist/QuoteCard.js.map +1 -0
  116. package/dist/Radio.d.ts +19 -0
  117. package/dist/Radio.js +161 -0
  118. package/dist/Radio.js.map +1 -0
  119. package/dist/RadioCards.d.ts +27 -0
  120. package/dist/RadioCards.js +205 -0
  121. package/dist/RadioCards.js.map +1 -0
  122. package/dist/ReactionBar.d.ts +15 -0
  123. package/dist/ReactionBar.js +92 -0
  124. package/dist/ReactionBar.js.map +1 -0
  125. package/dist/ReadingProgress.d.ts +8 -0
  126. package/dist/ReadingProgress.js +51 -0
  127. package/dist/ReadingProgress.js.map +1 -0
  128. package/dist/ReadingTime.d.ts +9 -0
  129. package/dist/ReadingTime.js +39 -0
  130. package/dist/ReadingTime.js.map +1 -0
  131. package/dist/Select.d.ts +20 -0
  132. package/dist/Select.js +244 -0
  133. package/dist/Select.js.map +1 -0
  134. package/dist/Separator.d.ts +14 -0
  135. package/dist/Separator.js +33 -0
  136. package/dist/Separator.js.map +1 -0
  137. package/dist/ShareButtons.d.ts +10 -0
  138. package/dist/ShareButtons.js +107 -0
  139. package/dist/ShareButtons.js.map +1 -0
  140. package/dist/Sidebar.d.ts +30 -0
  141. package/dist/Sidebar.js +140 -0
  142. package/dist/Sidebar.js.map +1 -0
  143. package/dist/Skeleton.d.ts +15 -0
  144. package/dist/Skeleton.js +67 -0
  145. package/dist/Skeleton.js.map +1 -0
  146. package/dist/Slider.d.ts +27 -0
  147. package/dist/Slider.js +182 -0
  148. package/dist/Slider.js.map +1 -0
  149. package/dist/Spinner.d.ts +12 -0
  150. package/dist/Spinner.js +24 -0
  151. package/dist/Spinner.js.map +1 -0
  152. package/dist/SpotlightCard.d.ts +17 -0
  153. package/dist/SpotlightCard.js +87 -0
  154. package/dist/SpotlightCard.js.map +1 -0
  155. package/dist/Stat.d.ts +19 -0
  156. package/dist/Stat.js +71 -0
  157. package/dist/Stat.js.map +1 -0
  158. package/dist/StatCard.d.ts +24 -0
  159. package/dist/StatCard.js +212 -0
  160. package/dist/StatCard.js.map +1 -0
  161. package/dist/Steps.d.ts +14 -0
  162. package/dist/Steps.js +94 -0
  163. package/dist/Steps.js.map +1 -0
  164. package/dist/Table.d.ts +21 -0
  165. package/dist/Table.js +93 -0
  166. package/dist/Table.js.map +1 -0
  167. package/dist/TableOfContents.d.ts +17 -0
  168. package/dist/TableOfContents.js +92 -0
  169. package/dist/TableOfContents.js.map +1 -0
  170. package/dist/Tabs.d.ts +17 -0
  171. package/dist/Tabs.js +123 -0
  172. package/dist/Tabs.js.map +1 -0
  173. package/dist/TagInput.d.ts +16 -0
  174. package/dist/TagInput.js +169 -0
  175. package/dist/TagInput.js.map +1 -0
  176. package/dist/Textarea.d.ts +13 -0
  177. package/dist/Textarea.js +74 -0
  178. package/dist/Textarea.js.map +1 -0
  179. package/dist/TimePicker.d.ts +28 -0
  180. package/dist/TimePicker.js +264 -0
  181. package/dist/TimePicker.js.map +1 -0
  182. package/dist/Timeline.d.ts +19 -0
  183. package/dist/Timeline.js +76 -0
  184. package/dist/Timeline.js.map +1 -0
  185. package/dist/Toast.d.ts +18 -0
  186. package/dist/Toast.js +318 -0
  187. package/dist/Toast.js.map +1 -0
  188. package/dist/Toggle.d.ts +13 -0
  189. package/dist/Toggle.js +84 -0
  190. package/dist/Toggle.js.map +1 -0
  191. package/dist/Tooltip.d.ts +13 -0
  192. package/dist/Tooltip.js +138 -0
  193. package/dist/Tooltip.js.map +1 -0
  194. package/dist/cli.js +172 -5
  195. package/dist/index.d.ts +68 -631
  196. package/dist/index.js +67 -5558
  197. package/dist/index.js.map +1 -1
  198. package/dist/registry/Accordion.tsx +6 -6
  199. package/dist/registry/Alert.tsx +13 -10
  200. package/dist/registry/AuthorCard.tsx +7 -7
  201. package/dist/registry/Avatar.tsx +5 -5
  202. package/dist/registry/Badge.tsx +12 -15
  203. package/dist/registry/Banner.tsx +15 -18
  204. package/dist/registry/Breadcrumb.tsx +3 -3
  205. package/dist/registry/Button.tsx +58 -69
  206. package/dist/registry/ButtonGroup.tsx +9 -9
  207. package/dist/registry/Card.tsx +15 -15
  208. package/dist/registry/CardShell.tsx +6 -6
  209. package/dist/registry/Carousel.tsx +19 -15
  210. package/dist/registry/Checkbox.tsx +14 -9
  211. package/dist/registry/CloseButton.tsx +2 -2
  212. package/dist/registry/CodeBlock.tsx +15 -15
  213. package/dist/registry/ColorPicker.tsx +404 -0
  214. package/dist/registry/Combobox.tsx +318 -0
  215. package/dist/registry/CommandPalette.tsx +31 -24
  216. package/dist/registry/ContextMenu.tsx +241 -0
  217. package/dist/registry/DataList.tsx +4 -4
  218. package/dist/registry/DatePicker.tsx +494 -0
  219. package/dist/registry/DateRangePicker.tsx +232 -0
  220. package/dist/registry/Drawer.tsx +17 -15
  221. package/dist/registry/DropdownMenu.tsx +39 -77
  222. package/dist/registry/EmptyState.tsx +8 -8
  223. package/dist/registry/FieldMessage.tsx +123 -0
  224. package/dist/registry/FileUpload.tsx +318 -0
  225. package/dist/registry/Floating.tsx +134 -0
  226. package/dist/registry/HoverCard.tsx +113 -0
  227. package/dist/registry/Input.tsx +54 -43
  228. package/dist/registry/Kbd.tsx +4 -5
  229. package/dist/registry/Modal.tsx +17 -15
  230. package/dist/registry/NumberInput.tsx +183 -0
  231. package/dist/registry/OtpInput.tsx +219 -0
  232. package/dist/registry/Pagination.tsx +15 -18
  233. package/dist/registry/Popover.tsx +40 -45
  234. package/dist/registry/Progress.tsx +4 -4
  235. package/dist/registry/QuoteCard.tsx +7 -7
  236. package/dist/registry/Radio.tsx +24 -21
  237. package/dist/registry/RadioCards.tsx +247 -0
  238. package/dist/registry/ReactionBar.tsx +5 -5
  239. package/dist/registry/ReadingProgress.tsx +2 -2
  240. package/dist/registry/ReadingTime.tsx +2 -2
  241. package/dist/registry/Select.tsx +28 -53
  242. package/dist/registry/Separator.tsx +52 -0
  243. package/dist/registry/ShareButtons.tsx +2 -2
  244. package/dist/registry/Sidebar.tsx +18 -18
  245. package/dist/registry/Skeleton.tsx +1 -1
  246. package/dist/registry/Slider.tsx +229 -0
  247. package/dist/registry/Spinner.tsx +32 -0
  248. package/dist/registry/SpotlightCard.tsx +6 -6
  249. package/dist/registry/Stat.tsx +4 -4
  250. package/dist/registry/StatCard.tsx +19 -19
  251. package/dist/registry/Steps.tsx +7 -7
  252. package/dist/registry/Table.tsx +12 -10
  253. package/dist/registry/TableOfContents.tsx +8 -8
  254. package/dist/registry/Tabs.tsx +6 -6
  255. package/dist/registry/TagInput.tsx +9 -9
  256. package/dist/registry/Textarea.tsx +5 -5
  257. package/dist/registry/TimePicker.tsx +315 -0
  258. package/dist/registry/Timeline.tsx +14 -14
  259. package/dist/registry/Toast.tsx +15 -12
  260. package/dist/registry/Toggle.tsx +7 -10
  261. package/dist/registry/Tooltip.tsx +21 -73
  262. package/dist/registry/styles.css +868 -62
  263. package/dist/registry/useFloatingPanel.ts +112 -0
  264. package/dist/styles.css +868 -62
  265. package/dist/useFloatingPanel.d.ts +24 -0
  266. package/dist/useFloatingPanel.js +89 -0
  267. package/dist/useFloatingPanel.js.map +1 -0
  268. package/dist/useModalBehavior.d.ts +15 -0
  269. package/dist/useModalBehavior.js +57 -0
  270. package/dist/useModalBehavior.js.map +1 -0
  271. package/package.json +1 -1
@@ -52,11 +52,11 @@ export function SpotlightCard({
52
52
  <div
53
53
  className={[
54
54
  "w-9 h-9 rounded-[11px] flex items-center justify-center mb-auto",
55
- dark ? "text-white" : "text-neutral-800",
55
+ dark ? "text-white" : "text-ink-soft",
56
56
  ].join(" ")}
57
57
  style={{
58
- background: dark ? "var(--tile-dark)" : "var(--face-light)",
59
- boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-light-shadow)",
58
+ background: dark ? "var(--tile-dark)" : "var(--tile)",
59
+ boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-shadow)",
60
60
  }}
61
61
  >
62
62
  {icon}
@@ -69,7 +69,7 @@ export function SpotlightCard({
69
69
  // Without an icon above to push it down, the copy still sits low
70
70
  icon ? "pt-8" : "mt-auto",
71
71
  "text-[14px] font-medium tracking-[-0.01em]",
72
- dark ? "text-neutral-400" : "text-neutral-500",
72
+ dark ? "text-neutral-400" : "text-ink-muted",
73
73
  ].join(" ")}
74
74
  >
75
75
  {eyebrow}
@@ -81,7 +81,7 @@ export function SpotlightCard({
81
81
  titleSizeBySize[size],
82
82
  !icon && !eyebrow ? "mt-auto" : "",
83
83
  "font-medium leading-[1.12] tracking-[-0.03em] text-balance",
84
- dark ? "text-white" : "text-neutral-900",
84
+ dark ? "text-white" : "text-ink",
85
85
  ].join(" ")}
86
86
  >
87
87
  {title}
@@ -91,7 +91,7 @@ export function SpotlightCard({
91
91
  <p
92
92
  className={[
93
93
  "text-[15px] leading-[1.6] max-w-lg text-pretty",
94
- dark ? "text-neutral-400" : "text-neutral-600",
94
+ dark ? "text-neutral-400" : "text-ink-body",
95
95
  ].join(" ")}
96
96
  >
97
97
  {subtitle}
@@ -43,16 +43,16 @@ export function Stat({ value, label, prefix = "", suffix = "", trend, className
43
43
  <div className={["flex flex-col gap-0.5", className].join(" ")}>
44
44
  <div className="flex items-baseline gap-1.5">
45
45
  <span
46
- className="text-[32px] font-semibold text-neutral-900 leading-none tabular-nums"
46
+ className="text-[32px] font-semibold text-ink leading-none tabular-nums"
47
47
  style={{ letterSpacing: "-0.03em" }}
48
48
  >
49
49
  {prefix}{displayed}{suffix}
50
50
  </span>
51
51
  {trend && (
52
- <span className="text-[12px] text-neutral-500 font-medium">{trend}</span>
52
+ <span className="text-[12px] text-ink-muted font-medium">{trend}</span>
53
53
  )}
54
54
  </div>
55
- <span className="text-[12px] text-neutral-500">{label}</span>
55
+ <span className="text-[12px] text-ink-muted">{label}</span>
56
56
  </div>
57
57
  );
58
58
  }
@@ -65,7 +65,7 @@ interface StatRowProps {
65
65
  export function StatRow({ children, className = "" }: StatRowProps) {
66
66
  const arr = React.Children.toArray(children);
67
67
  return (
68
- <div className={["grid grid-cols-2 gap-6 sm:flex sm:divide-x sm:divide-neutral-200 sm:gap-0", className].join(" ")}>
68
+ <div className={["grid grid-cols-2 gap-6 sm:flex sm:divide-x sm:divide-line-strong sm:gap-0", className].join(" ")}>
69
69
  {arr.map((child, i) => (
70
70
  <div
71
71
  key={i}
@@ -76,7 +76,7 @@ function Sparkline({ data, dark }: { data: number[]; dark?: boolean }) {
76
76
  // The fill runs off both edges of the face so it reads as part of the surface
77
77
  const area = `M0,${pts[0][1].toFixed(1)} ${line.replace(/^M/, "L")} L${W},${pts[pts.length - 1][1].toFixed(1)} L${W},${H} L0,${H} Z`;
78
78
 
79
- const stroke = dark ? "#ffffff" : "#171717";
79
+ const stroke = dark ? "var(--spark-dark)" : "var(--spark)";
80
80
  const gradId = `spark-fade-${uid.replace(/:/g, "")}`;
81
81
  const [lx, ly] = pts[pts.length - 1];
82
82
 
@@ -86,22 +86,22 @@ function Sparkline({ data, dark }: { data: number[]; dark?: boolean }) {
86
86
  className="relative h-14"
87
87
  // Fades in from the left so the line arrives rather than starts at a hard edge
88
88
  style={{
89
- maskImage: "linear-gradient(90deg, transparent 0%, #000 18%)",
90
- WebkitMaskImage: "linear-gradient(90deg, transparent 0%, #000 18%)",
89
+ maskImage: "linear-gradient(90deg, transparent 0%, black 18%)",
90
+ WebkitMaskImage: "linear-gradient(90deg, transparent 0%, black 18%)",
91
91
  }}
92
92
  >
93
93
  <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" className="absolute inset-0 w-full h-full overflow-visible">
94
94
  <defs>
95
95
  <linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
96
- <stop offset="0%" stopColor={stroke} stopOpacity={dark ? 0.2 : 0.08} />
97
- <stop offset="100%" stopColor={stroke} stopOpacity="0" />
96
+ <stop offset="0%" style={{ stopColor: stroke }} stopOpacity={dark ? 0.2 : 0.08} />
97
+ <stop offset="100%" style={{ stopColor: stroke }} stopOpacity="0" />
98
98
  </linearGradient>
99
99
  </defs>
100
100
  <path d={area} fill={`url(#${gradId})`} />
101
101
  <path
102
102
  d={line}
103
103
  fill="none"
104
- stroke={stroke}
104
+ style={{ stroke }}
105
105
  strokeOpacity={dark ? 0.85 : 0.9}
106
106
  strokeWidth={1.5}
107
107
  strokeLinecap="round"
@@ -116,7 +116,7 @@ function Sparkline({ data, dark }: { data: number[]; dark?: boolean }) {
116
116
  left: `${(lx / W) * 100}%`,
117
117
  top: `${(ly / H) * 100}%`,
118
118
  background: stroke,
119
- boxShadow: dark ? "0 0 0 3px rgba(255,255,255,0.14)" : "0 0 0 3px rgba(17,17,17,0.08)",
119
+ boxShadow: dark ? "0 0 0 3px var(--spark-dark-halo)" : "0 0 0 3px var(--spark-halo)",
120
120
  }}
121
121
  />
122
122
  </div>
@@ -124,9 +124,9 @@ function Sparkline({ data, dark }: { data: number[]; dark?: boolean }) {
124
124
  }
125
125
 
126
126
  const deltaTone: Record<DeltaDirection, { light: string; dark: string; label: string }> = {
127
- up: { light: "text-emerald-700", dark: "text-emerald-400", label: "up" },
128
- down: { light: "text-red-600", dark: "text-red-400", label: "down" },
129
- neutral: { light: "text-neutral-600", dark: "text-neutral-300", label: "unchanged" },
127
+ up: { light: "text-success", dark: "text-emerald-400", label: "up" },
128
+ down: { light: "text-danger", dark: "text-red-400", label: "down" },
129
+ neutral: { light: "text-ink-body", dark: "text-neutral-300", label: "unchanged" },
130
130
  };
131
131
 
132
132
  function DeltaArrow({ direction }: { direction: DeltaDirection }) {
@@ -171,8 +171,8 @@ export function StatCard({
171
171
  dark ? deltaTone[delta.direction].dark : deltaTone[delta.direction].light,
172
172
  ].join(" ")}
173
173
  style={{
174
- background: dark ? "var(--tile-dark)" : "var(--face-light)",
175
- boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-light-shadow)",
174
+ background: dark ? "var(--tile-dark)" : "var(--tile)",
175
+ boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-shadow)",
176
176
  }}
177
177
  >
178
178
  <DeltaArrow direction={delta.direction} />
@@ -192,11 +192,11 @@ export function StatCard({
192
192
  aria-hidden
193
193
  className={[
194
194
  "w-7 h-7 rounded-[9px] flex items-center justify-center shrink-0",
195
- dark ? "text-neutral-100" : "text-neutral-600",
195
+ dark ? "text-neutral-100" : "text-ink-body",
196
196
  ].join(" ")}
197
197
  style={{
198
- background: dark ? "var(--tile-dark)" : "var(--face-light)",
199
- boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-light-shadow)",
198
+ background: dark ? "var(--tile-dark)" : "var(--tile)",
199
+ boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-shadow)",
200
200
  }}
201
201
  >
202
202
  {icon}
@@ -205,7 +205,7 @@ export function StatCard({
205
205
  <p
206
206
  className={[
207
207
  "text-[14px] font-medium tracking-[-0.01em] truncate",
208
- dark ? "text-neutral-300" : "text-neutral-700",
208
+ dark ? "text-neutral-300" : "text-ink-label",
209
209
  ].join(" ")}
210
210
  >
211
211
  {label}
@@ -220,7 +220,7 @@ export function StatCard({
220
220
  aria-hidden
221
221
  className={[
222
222
  "px-6 mt-5 text-[44px] font-semibold tabular-nums tracking-[-0.045em] leading-none",
223
- dark ? "text-white" : "text-neutral-900",
223
+ dark ? "text-white" : "text-ink",
224
224
  ].join(" ")}
225
225
  >
226
226
  {prefix}
@@ -230,7 +230,7 @@ export function StatCard({
230
230
  <span
231
231
  className={[
232
232
  "ml-1 text-[17px] font-medium tracking-[-0.01em]",
233
- dark ? "text-neutral-500" : "text-neutral-400",
233
+ dark ? "text-neutral-500" : "text-ink-faint",
234
234
  ].join(" ")}
235
235
  >
236
236
  {suffix.trim()}
@@ -238,7 +238,7 @@ export function StatCard({
238
238
  )}
239
239
  </p>
240
240
  {hint && (
241
- <p className={["px-6 mt-2.5 text-[13px] truncate", dark ? "text-neutral-500" : "text-neutral-500"].join(" ")}>
241
+ <p className={["px-6 mt-2.5 text-[13px] truncate", dark ? "text-neutral-500" : "text-ink-muted"].join(" ")}>
242
242
  {hint}
243
243
  </p>
244
244
  )}
@@ -62,7 +62,7 @@ export function Steps({ steps, current, className = "" }: StepsProps) {
62
62
  <span
63
63
  className="block h-full rounded-full origin-left transition-transform duration-500 ease-[var(--ease-out-soft)]"
64
64
  style={{
65
- background: "var(--face-dark)",
65
+ background: "var(--face-inverse)",
66
66
  transform: isCompleted ? "scaleX(1)" : "scaleX(0)",
67
67
  }}
68
68
  />
@@ -73,14 +73,14 @@ export function Steps({ steps, current, className = "" }: StepsProps) {
73
73
  className={[
74
74
  "relative w-[30px] h-[30px] rounded-full flex items-center justify-center text-[12.5px] font-semibold tabular-nums shrink-0",
75
75
  "transition-[box-shadow,color] duration-300",
76
- reached ? "text-white" : "text-neutral-400",
76
+ reached ? "text-ink-inverse" : "text-ink-faint",
77
77
  ].join(" ")}
78
78
  style={{
79
- background: reached ? "var(--face-dark)" : "var(--field)",
79
+ background: reached ? "var(--face-inverse)" : "var(--field)",
80
80
  boxShadow: isCurrent
81
- ? "var(--face-dark-shadow), 0 0 0 4px rgba(17,17,17,0.06), 0 0 0 5px rgba(17,17,17,0.03)"
81
+ ? "var(--face-inverse-shadow), var(--halo)"
82
82
  : reached
83
- ? "var(--face-dark-shadow)"
83
+ ? "var(--face-inverse-shadow)"
84
84
  : "var(--field-shadow)",
85
85
  }}
86
86
  >
@@ -92,13 +92,13 @@ export function Steps({ steps, current, className = "" }: StepsProps) {
92
92
  <span
93
93
  className={[
94
94
  "text-[13px] font-medium leading-tight transition-colors duration-200",
95
- reached ? "text-neutral-900" : "text-neutral-500",
95
+ reached ? "text-ink" : "text-ink-muted",
96
96
  ].join(" ")}
97
97
  >
98
98
  {step.label}
99
99
  </span>
100
100
  {step.description && (
101
- <span className="text-[12px] text-neutral-400 leading-snug">{step.description}</span>
101
+ <span className="text-[12px] text-ink-faint leading-snug">{step.description}</span>
102
102
  )}
103
103
  </span>
104
104
  </li>
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React from "react";
2
4
 
3
5
  interface Column<T> {
@@ -36,26 +38,26 @@ export function Table<T extends Record<string, unknown>>({
36
38
  >
37
39
  {(title || description) && (
38
40
  <div className="px-3.5 pt-3.5 pb-3">
39
- {title && <p className="text-[13px] font-semibold text-neutral-900 tracking-[-0.01em]">{title}</p>}
40
- {description && <p className="text-[12px] text-neutral-500 mt-0.5">{description}</p>}
41
+ {title && <p className="text-[13px] font-semibold text-ink tracking-[-0.01em]">{title}</p>}
42
+ {description && <p className="text-[12px] text-ink-muted mt-0.5">{description}</p>}
41
43
  </div>
42
44
  )}
43
45
 
44
46
  <div
45
- className="bg-white rounded-[13px] overflow-x-auto scroll-thin"
47
+ className="bg-panel-face rounded-[13px] overflow-x-auto scroll-thin"
46
48
  style={{ boxShadow: "var(--panel-face-shadow)" }}
47
49
  >
48
50
  <table className="w-full text-left border-collapse">
49
51
  {title && <caption className="sr-only">{title}</caption>}
50
52
  <thead>
51
- <tr className="border-b border-neutral-100">
53
+ <tr className="border-b border-line">
52
54
  {columns.map((col) => (
53
55
  <th
54
56
  key={String(col.key)}
55
57
  scope="col"
56
58
  style={col.width ? { width: col.width } : undefined}
57
59
  className={[
58
- "px-4 pt-3 pb-2.5 text-[12px] font-medium text-neutral-500 lowercase whitespace-nowrap",
60
+ "px-4 pt-3 pb-2.5 text-[12px] font-medium text-ink-muted lowercase whitespace-nowrap",
59
61
  alignClass(col.align),
60
62
  ].join(" ")}
61
63
  >
@@ -82,9 +84,9 @@ export function Table<T extends Record<string, unknown>>({
82
84
  : undefined
83
85
  }
84
86
  className={[
85
- "border-b border-neutral-100 last:border-0 transition-colors duration-100",
87
+ "border-b border-line last:border-0 transition-colors duration-100",
86
88
  onRowClick
87
- ? "cursor-pointer hover:bg-neutral-50 focus-visible:bg-neutral-50 outline-none focus-visible:shadow-[inset_2px_0_0_#171717]"
89
+ ? "cursor-pointer hover:bg-surface-hover focus-visible:bg-surface-hover outline-none focus-visible:shadow-[inset_2px_0_0_var(--ink)]"
88
90
  : "",
89
91
  ].join(" ")}
90
92
  >
@@ -95,7 +97,7 @@ export function Table<T extends Record<string, unknown>>({
95
97
  key={String(col.key)}
96
98
  className={[
97
99
  "px-4 py-3 text-[13px]",
98
- ci === 0 ? "font-medium text-neutral-900" : "text-neutral-600 tabular-nums",
100
+ ci === 0 ? "font-medium text-ink" : "text-ink-body tabular-nums",
99
101
  alignClass(col.align),
100
102
  ].join(" ")}
101
103
  >
@@ -108,8 +110,8 @@ export function Table<T extends Record<string, unknown>>({
108
110
  {rows.length === 0 && (
109
111
  <tr>
110
112
  <td colSpan={columns.length} className="px-5 py-12 text-center">
111
- <p className="text-[13px] font-medium text-neutral-700">nothing here yet</p>
112
- <p className="text-[12px] text-neutral-500 mt-0.5">rows will show up here once there are some.</p>
113
+ <p className="text-[13px] font-medium text-ink-label">nothing here yet</p>
114
+ <p className="text-[12px] text-ink-muted mt-0.5">rows will show up here once there are some.</p>
113
115
  </td>
114
116
  </tr>
115
117
  )}
@@ -64,19 +64,19 @@ export function TableOfContents({
64
64
  style={{ background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" }}
65
65
  >
66
66
  <div
67
- className="bg-white rounded-[13px] px-5 pt-4 pb-4"
67
+ className="bg-panel-face rounded-[13px] px-5 pt-4 pb-4"
68
68
  style={{ boxShadow: "var(--panel-face-shadow)" }}
69
69
  >
70
- <p aria-hidden className="text-[12px] font-medium text-neutral-400 mb-3">
70
+ <p aria-hidden className="text-[12px] font-medium text-ink-faint mb-3">
71
71
  on this page
72
72
  </p>
73
73
 
74
74
  <div className="relative">
75
- <span aria-hidden className="absolute left-0 top-0 bottom-0 w-px bg-neutral-900/[0.08]" />
75
+ <span aria-hidden className="absolute left-0 top-0 bottom-0 w-px bg-ink/[0.08]" />
76
76
  {marker && (
77
77
  <span
78
78
  aria-hidden
79
- className="absolute -left-[0.5px] w-[2px] rounded-full bg-neutral-900"
79
+ className="absolute -left-[0.5px] w-[2px] rounded-full bg-ink"
80
80
  style={{ top: marker.top + 6, height: marker.height - 12, transition: slide }}
81
81
  />
82
82
  )}
@@ -96,12 +96,12 @@ export function TableOfContents({
96
96
  // Weight stays fixed so the active item doesn't reflow its line
97
97
  "relative block w-full text-left py-1.5 text-[13px] leading-5 rounded-[6px] cursor-pointer",
98
98
  "transition-colors duration-200 outline-none",
99
- "focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-neutral-900",
99
+ "focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ink",
100
100
  isActive
101
- ? "text-neutral-900 font-medium"
101
+ ? "text-ink font-medium"
102
102
  : item.level === 1
103
- ? "text-neutral-600 hover:text-neutral-900"
104
- : "text-neutral-400 hover:text-neutral-700",
103
+ ? "text-ink-body hover:text-ink"
104
+ : "text-ink-faint hover:text-ink-label",
105
105
  ].join(" ")}
106
106
  style={{ paddingLeft: indent[item.level] }}
107
107
  >
@@ -34,8 +34,8 @@ export function Tabs({ tabs, defaultTab, onChange, className = "" }: TabsProps)
34
34
  setPill({ left: br.left - lr.left, width: br.width, ready: true });
35
35
  };
36
36
 
37
- useLayoutEffect(() => { measurePill(active); }, []);
38
- useEffect(() => { measurePill(active); }, [active]);
37
+ // Before paint, so the thumb is never seen out of place
38
+ useLayoutEffect(() => { measurePill(active); }, [active]);
39
39
 
40
40
  useEffect(() => {
41
41
  const ro = new ResizeObserver(() => measurePill(active));
@@ -83,7 +83,7 @@ export function Tabs({ tabs, defaultTab, onChange, className = "" }: TabsProps)
83
83
  style={{
84
84
  left: pill.left,
85
85
  width: pill.width,
86
- background: "var(--face-light)",
86
+ background: "var(--face)",
87
87
  boxShadow: "var(--thumb-shadow)",
88
88
  transition: "left 300ms var(--ease-spring), width 300ms var(--ease-spring)",
89
89
  }}
@@ -106,8 +106,8 @@ export function Tabs({ tabs, defaultTab, onChange, className = "" }: TabsProps)
106
106
  className={[
107
107
  "relative z-10 flex-1 sm:flex-none px-3.5 py-2 rounded-[9px] text-[13px] font-medium text-center whitespace-nowrap cursor-pointer",
108
108
  "transition-colors duration-150",
109
- "focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-neutral-900",
110
- isActive ? "text-neutral-900" : "text-neutral-500 hover:text-neutral-800",
109
+ "focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ink",
110
+ isActive ? "text-ink" : "text-ink-muted hover:text-ink-soft",
111
111
  ].join(" ")}
112
112
  >
113
113
  {tab.label}
@@ -124,7 +124,7 @@ export function Tabs({ tabs, defaultTab, onChange, className = "" }: TabsProps)
124
124
  aria-labelledby={`${uid}-tab-${active}`}
125
125
  tabIndex={0}
126
126
  className={[
127
- "text-[13px] text-neutral-600 leading-relaxed rounded-lg focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-neutral-900",
127
+ "text-[13px] text-ink-body leading-relaxed rounded-lg focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ink",
128
128
  direction === "right" ? "animate-tab-slide-right" : "animate-tab-slide-left",
129
129
  ].join(" ")}
130
130
  >
@@ -120,7 +120,7 @@ export function TagInput({
120
120
  return (
121
121
  <div className={["flex flex-col gap-1.5", fullWidth ? "w-full" : ""].join(" ")}>
122
122
  {label && (
123
- <label htmlFor={`${uid}-input`} className="text-[12.5px] font-medium text-neutral-700 tracking-[-0.005em]">
123
+ <label htmlFor={`${uid}-input`} className="text-[12.5px] font-medium text-ink-label tracking-[-0.005em]">
124
124
  {label}
125
125
  </label>
126
126
  )}
@@ -131,18 +131,18 @@ export function TagInput({
131
131
  "transition-[box-shadow,background] duration-200 ease-out cursor-text",
132
132
  "[background:var(--field)] [box-shadow:var(--field-shadow)]",
133
133
  "hover:[background:var(--field-hover)] hover:[box-shadow:var(--field-shadow-hover)]",
134
- "focus-within:[background:#ffffff] focus-within:[box-shadow:var(--field-shadow-focus)]",
134
+ "focus-within:[background:var(--field-focus)] focus-within:[box-shadow:var(--field-shadow-focus)]",
135
135
  fullWidth ? "w-full" : "",
136
136
  className,
137
137
  ]
138
138
  .filter(Boolean)
139
139
  .join(" ")}
140
140
  >
141
- {animTags.map((at, i) => (
141
+ {animTags.map((at) => (
142
142
  <span
143
143
  key={at.text}
144
144
  className={[
145
- "inline-flex items-center gap-1 pl-2 pr-1 py-0.5 rounded-[7px] text-[11.5px] font-medium text-neutral-700 select-none",
145
+ "inline-flex items-center gap-1 pl-2 pr-1 py-0.5 rounded-[7px] text-[11.5px] font-medium text-ink-label select-none",
146
146
  "transition-all duration-180 ease-[cubic-bezier(0.34,1.4,0.64,1)]",
147
147
  at.state === "entering" ? "opacity-0 scale-75" : "",
148
148
  at.state === "visible" ? "opacity-100 scale-100" : "",
@@ -150,8 +150,8 @@ export function TagInput({
150
150
  ].join(" ")}
151
151
  style={{
152
152
  /* Tags are small raised faces sitting in the sunken field */
153
- background: "var(--face-light)",
154
- boxShadow: "var(--face-light-shadow)",
153
+ background: "var(--face)",
154
+ boxShadow: "var(--face-shadow)",
155
155
  }}
156
156
  >
157
157
  {at.text}
@@ -163,7 +163,7 @@ export function TagInput({
163
163
  if (idx !== -1) removeTag(idx);
164
164
  }}
165
165
  aria-label={`Remove ${at.text}`}
166
- className="w-4 h-4 inline-flex items-center justify-center rounded-[5px] text-neutral-400 hover:text-neutral-900 hover:bg-neutral-900/[0.06] cursor-pointer transition-colors"
166
+ className="w-4 h-4 inline-flex items-center justify-center rounded-[5px] text-ink-faint hover:text-ink hover:bg-ink/[0.06] cursor-pointer transition-colors"
167
167
  >
168
168
  <svg width="8" height="8" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
169
169
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
@@ -182,10 +182,10 @@ export function TagInput({
182
182
  placeholder={tags.length === 0 ? placeholder : ""}
183
183
  aria-label={label ? undefined : placeholder}
184
184
  aria-describedby={hint ? `${uid}-hint` : undefined}
185
- className="flex-1 min-w-[80px] bg-transparent text-[13.5px] text-neutral-900 placeholder:text-neutral-400 outline-none py-0.5"
185
+ className="flex-1 min-w-[80px] bg-transparent text-[13.5px] text-ink placeholder:text-ink-faint outline-none py-0.5"
186
186
  />
187
187
  </div>
188
- {hint && <p id={`${uid}-hint`} className="text-[11.5px] text-neutral-500 mt-0.5">{hint}</p>}
188
+ {hint && <p id={`${uid}-hint`} className="text-[11.5px] text-ink-muted mt-0.5">{hint}</p>}
189
189
  </div>
190
190
  );
191
191
  }
@@ -44,7 +44,7 @@ export function Textarea({
44
44
  {label && (
45
45
  <label
46
46
  htmlFor={textareaId}
47
- className="text-[12.5px] font-medium text-neutral-700 tracking-[-0.005em] transition-colors duration-200 group-focus-within/field:text-neutral-950"
47
+ className="text-[12.5px] font-medium text-ink-label tracking-[-0.005em] transition-colors duration-200 group-focus-within/field:text-ink-strong"
48
48
  >
49
49
  {label}
50
50
  </label>
@@ -55,8 +55,8 @@ export function Textarea({
55
55
  rows={rows}
56
56
  onChange={handleChange}
57
57
  className={[
58
- "field rounded-xl px-4 py-3 text-[13.5px] text-neutral-900 resize-none scroll-thin",
59
- "placeholder:text-neutral-400 placeholder:transition-colors placeholder:duration-200 focus:placeholder:text-neutral-300",
58
+ "field rounded-xl px-4 py-3 text-[13.5px] text-ink resize-none scroll-thin",
59
+ "placeholder:text-ink-faint placeholder:transition-colors placeholder:duration-200 focus:placeholder:text-ink-ghost",
60
60
  "disabled:opacity-40 disabled:cursor-not-allowed",
61
61
  fullWidth ? "w-full" : "",
62
62
  className,
@@ -68,7 +68,7 @@ export function Textarea({
68
68
  {...rest}
69
69
  />
70
70
  {error && (
71
- <p id={messageId} role="alert" className="text-[11.5px] text-red-600 font-medium flex items-center gap-1 mt-0.5">
71
+ <p id={messageId} role="alert" className="text-[11.5px] text-danger font-medium flex items-center gap-1 mt-0.5">
72
72
  <svg width="11" height="11" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
73
73
  <circle cx="12" cy="12" r="10" />
74
74
  <path strokeLinecap="round" d="M12 8v4m0 4h.01" />
@@ -77,7 +77,7 @@ export function Textarea({
77
77
  </p>
78
78
  )}
79
79
  {hint && !error && (
80
- <p id={messageId} className="text-[11.5px] text-neutral-500 mt-0.5">{hint}</p>
80
+ <p id={messageId} className="text-[11.5px] text-ink-muted mt-0.5">{hint}</p>
81
81
  )}
82
82
  </div>
83
83
  );