@robr0/design-system 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 (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/charts.d.ts +14 -0
  4. package/charts.js +20 -0
  5. package/components/Accordion/Accordion.css +119 -0
  6. package/components/Accordion/Accordion.d.ts +24 -0
  7. package/components/Accordion/Accordion.js +76 -0
  8. package/components/Alert/Alert.css +252 -0
  9. package/components/Alert/Alert.d.ts +24 -0
  10. package/components/Alert/Alert.js +53 -0
  11. package/components/AlertDialog/AlertDialog.css +131 -0
  12. package/components/AlertDialog/AlertDialog.d.ts +29 -0
  13. package/components/AlertDialog/AlertDialog.js +143 -0
  14. package/components/AppLayout/AppLayout.css +52 -0
  15. package/components/AppLayout/AppLayout.d.ts +27 -0
  16. package/components/AppLayout/AppLayout.js +45 -0
  17. package/components/AppSidebar/AppSidebar.css +434 -0
  18. package/components/AppSidebar/AppSidebar.d.ts +67 -0
  19. package/components/AppSidebar/AppSidebar.js +272 -0
  20. package/components/Avatar/Avatar.css +133 -0
  21. package/components/Avatar/Avatar.d.ts +20 -0
  22. package/components/Avatar/Avatar.js +56 -0
  23. package/components/Avatar/demoAvatars.d.ts +14 -0
  24. package/components/Avatar/demoAvatars.js +16 -0
  25. package/components/Badge/Badge.css +68 -0
  26. package/components/Badge/Badge.d.ts +13 -0
  27. package/components/Badge/Badge.js +15 -0
  28. package/components/Breadcrumb/Breadcrumb.css +92 -0
  29. package/components/Breadcrumb/Breadcrumb.d.ts +19 -0
  30. package/components/Breadcrumb/Breadcrumb.js +39 -0
  31. package/components/Button/Button.css +381 -0
  32. package/components/Button/Button.d.ts +39 -0
  33. package/components/Button/Button.js +71 -0
  34. package/components/ButtonGroup/ButtonGroup.css +39 -0
  35. package/components/ButtonGroup/ButtonGroup.d.ts +13 -0
  36. package/components/ButtonGroup/ButtonGroup.js +37 -0
  37. package/components/Card/Card.css +169 -0
  38. package/components/Card/Card.d.ts +30 -0
  39. package/components/Card/Card.js +90 -0
  40. package/components/Carousel/Carousel.css +112 -0
  41. package/components/Carousel/Carousel.d.ts +25 -0
  42. package/components/Carousel/Carousel.js +126 -0
  43. package/components/Chart/AreaChart.d.ts +36 -0
  44. package/components/Chart/AreaChart.js +99 -0
  45. package/components/Chart/BarChart.d.ts +34 -0
  46. package/components/Chart/BarChart.js +107 -0
  47. package/components/Chart/Chart.css +176 -0
  48. package/components/Chart/LineChart.d.ts +34 -0
  49. package/components/Chart/LineChart.js +90 -0
  50. package/components/Chart/PieChart.d.ts +34 -0
  51. package/components/Chart/PieChart.js +106 -0
  52. package/components/Chart/RadarChart.d.ts +34 -0
  53. package/components/Chart/RadarChart.js +91 -0
  54. package/components/Chart/RadialChart.d.ts +37 -0
  55. package/components/Chart/RadialChart.js +119 -0
  56. package/components/Chart/ScatterChart.d.ts +36 -0
  57. package/components/Chart/ScatterChart.js +109 -0
  58. package/components/Chart/StackedBarChart.d.ts +34 -0
  59. package/components/Chart/StackedBarChart.js +111 -0
  60. package/components/Chart/Treemap.d.ts +33 -0
  61. package/components/Chart/Treemap.js +109 -0
  62. package/components/Checkbox/Checkbox.css +164 -0
  63. package/components/Checkbox/Checkbox.d.ts +45 -0
  64. package/components/Checkbox/Checkbox.js +125 -0
  65. package/components/Chip/Chip.css +195 -0
  66. package/components/Chip/Chip.d.ts +27 -0
  67. package/components/Chip/Chip.js +80 -0
  68. package/components/CircularButton/CircularButton.css +206 -0
  69. package/components/CircularButton/CircularButton.d.ts +30 -0
  70. package/components/CircularButton/CircularButton.js +54 -0
  71. package/components/CodeBlock/CodeBlock.css +180 -0
  72. package/components/CodeBlock/CodeBlock.d.ts +23 -0
  73. package/components/CodeBlock/CodeBlock.js +85 -0
  74. package/components/ColourSwatch/ColourSwatch.css +88 -0
  75. package/components/ColourSwatch/ColourSwatch.d.ts +34 -0
  76. package/components/ColourSwatch/ColourSwatch.js +42 -0
  77. package/components/Combobox/Combobox.css +411 -0
  78. package/components/Combobox/Combobox.d.ts +73 -0
  79. package/components/Combobox/Combobox.js +312 -0
  80. package/components/CommandPalette/CommandPalette.css +310 -0
  81. package/components/CommandPalette/CommandPalette.d.ts +60 -0
  82. package/components/CommandPalette/CommandPalette.js +263 -0
  83. package/components/ContactCard/ContactCard.css +123 -0
  84. package/components/ContactCard/ContactCard.d.ts +23 -0
  85. package/components/ContactCard/ContactCard.js +78 -0
  86. package/components/ContributionGraph/ContributionGraph.css +142 -0
  87. package/components/ContributionGraph/ContributionGraph.d.ts +26 -0
  88. package/components/ContributionGraph/ContributionGraph.js +125 -0
  89. package/components/DateInput/DateInput.css +163 -0
  90. package/components/DateInput/DateInput.d.ts +43 -0
  91. package/components/DateInput/DateInput.js +69 -0
  92. package/components/DatePicker/DatePicker.css +198 -0
  93. package/components/DatePicker/DatePicker.d.ts +22 -0
  94. package/components/DatePicker/DatePicker.js +170 -0
  95. package/components/Dialog/Dialog.css +176 -0
  96. package/components/Dialog/Dialog.d.ts +28 -0
  97. package/components/Dialog/Dialog.js +132 -0
  98. package/components/Divider/Divider.css +100 -0
  99. package/components/Divider/Divider.d.ts +18 -0
  100. package/components/Divider/Divider.js +31 -0
  101. package/components/Drawer/Drawer.css +262 -0
  102. package/components/Drawer/Drawer.d.ts +30 -0
  103. package/components/Drawer/Drawer.js +134 -0
  104. package/components/Dropdown/Dropdown.css +300 -0
  105. package/components/Dropdown/Dropdown.d.ts +48 -0
  106. package/components/Dropdown/Dropdown.js +196 -0
  107. package/components/DropdownMenu/DropdownMenu.css +303 -0
  108. package/components/DropdownMenu/DropdownMenu.d.ts +42 -0
  109. package/components/DropdownMenu/DropdownMenu.js +322 -0
  110. package/components/EmptyState/EmptyState.css +115 -0
  111. package/components/EmptyState/EmptyState.d.ts +27 -0
  112. package/components/EmptyState/EmptyState.js +32 -0
  113. package/components/EntityCard/EntityCard.css +69 -0
  114. package/components/EntityCard/EntityCard.d.ts +18 -0
  115. package/components/EntityCard/EntityCard.js +26 -0
  116. package/components/Figure/Figure.css +61 -0
  117. package/components/Figure/Figure.d.ts +17 -0
  118. package/components/Figure/Figure.js +35 -0
  119. package/components/FileInput/FileInput.css +308 -0
  120. package/components/FileInput/FileInput.d.ts +54 -0
  121. package/components/FileInput/FileInput.js +177 -0
  122. package/components/Input/Input.css +193 -0
  123. package/components/Input/Input.d.ts +40 -0
  124. package/components/Input/Input.js +71 -0
  125. package/components/Instructions/Instructions.css +216 -0
  126. package/components/Instructions/Instructions.d.ts +28 -0
  127. package/components/Instructions/Instructions.js +32 -0
  128. package/components/LinkList/LinkList.css +71 -0
  129. package/components/LinkList/LinkList.d.ts +20 -0
  130. package/components/LinkList/LinkList.js +41 -0
  131. package/components/MotionSwatch/MotionSwatch.css +176 -0
  132. package/components/MotionSwatch/MotionSwatch.d.ts +25 -0
  133. package/components/MotionSwatch/MotionSwatch.js +74 -0
  134. package/components/Nav/Nav.css +51 -0
  135. package/components/Nav/Nav.d.ts +19 -0
  136. package/components/Nav/Nav.js +26 -0
  137. package/components/Pagination/Pagination.css +126 -0
  138. package/components/Pagination/Pagination.d.ts +22 -0
  139. package/components/Pagination/Pagination.js +83 -0
  140. package/components/Popover/Popover.css +99 -0
  141. package/components/Popover/Popover.d.ts +28 -0
  142. package/components/Popover/Popover.js +102 -0
  143. package/components/ProgressBar/ProgressBar.css +60 -0
  144. package/components/ProgressBar/ProgressBar.d.ts +16 -0
  145. package/components/ProgressBar/ProgressBar.js +27 -0
  146. package/components/Quote/Quote.css +79 -0
  147. package/components/Quote/Quote.d.ts +18 -0
  148. package/components/Quote/Quote.js +26 -0
  149. package/components/RadioButton/RadioButton.css +132 -0
  150. package/components/RadioButton/RadioButton.d.ts +43 -0
  151. package/components/RadioButton/RadioButton.js +77 -0
  152. package/components/SectionTitle/SectionTitle.css +37 -0
  153. package/components/SectionTitle/SectionTitle.d.ts +10 -0
  154. package/components/SectionTitle/SectionTitle.js +17 -0
  155. package/components/SegmentedControl/SegmentedControl.css +129 -0
  156. package/components/SegmentedControl/SegmentedControl.d.ts +33 -0
  157. package/components/SegmentedControl/SegmentedControl.js +91 -0
  158. package/components/SelectionCard/SelectionCard.css +251 -0
  159. package/components/SelectionCard/SelectionCard.d.ts +26 -0
  160. package/components/SelectionCard/SelectionCard.js +85 -0
  161. package/components/Skeleton/Skeleton.css +79 -0
  162. package/components/Skeleton/Skeleton.d.ts +16 -0
  163. package/components/Skeleton/Skeleton.js +30 -0
  164. package/components/Slider/Slider.css +86 -0
  165. package/components/Slider/Slider.d.ts +25 -0
  166. package/components/Slider/Slider.js +46 -0
  167. package/components/SpacingSwatch/SpacingSwatch.css +108 -0
  168. package/components/SpacingSwatch/SpacingSwatch.d.ts +20 -0
  169. package/components/SpacingSwatch/SpacingSwatch.js +50 -0
  170. package/components/Spinner/Spinner.css +73 -0
  171. package/components/Spinner/Spinner.d.ts +14 -0
  172. package/components/Spinner/Spinner.js +42 -0
  173. package/components/Stat/Stat.css +78 -0
  174. package/components/Stat/Stat.d.ts +19 -0
  175. package/components/Stat/Stat.js +30 -0
  176. package/components/Table/Table.css +186 -0
  177. package/components/Table/Table.d.ts +45 -0
  178. package/components/Table/Table.js +65 -0
  179. package/components/Tabs/Tabs.css +140 -0
  180. package/components/Tabs/Tabs.d.ts +28 -0
  181. package/components/Tabs/Tabs.js +77 -0
  182. package/components/Textarea/Textarea.css +152 -0
  183. package/components/Textarea/Textarea.d.ts +40 -0
  184. package/components/Textarea/Textarea.js +74 -0
  185. package/components/Timeline/Timeline.css +339 -0
  186. package/components/Timeline/Timeline.d.ts +63 -0
  187. package/components/Timeline/Timeline.js +69 -0
  188. package/components/Toast/Toast.css +342 -0
  189. package/components/Toast/Toast.d.ts +63 -0
  190. package/components/Toast/Toast.js +213 -0
  191. package/components/ToggleGroup/ToggleGroup.css +84 -0
  192. package/components/ToggleGroup/ToggleGroup.d.ts +30 -0
  193. package/components/ToggleGroup/ToggleGroup.js +53 -0
  194. package/components/ToggleSwitch/ToggleSwitch.css +139 -0
  195. package/components/ToggleSwitch/ToggleSwitch.d.ts +23 -0
  196. package/components/ToggleSwitch/ToggleSwitch.js +50 -0
  197. package/components/Tooltip/Tooltip.css +122 -0
  198. package/components/Tooltip/Tooltip.d.ts +21 -0
  199. package/components/Tooltip/Tooltip.js +63 -0
  200. package/components/TypographySwatch/TypographySwatch.css +81 -0
  201. package/components/TypographySwatch/TypographySwatch.d.ts +24 -0
  202. package/components/TypographySwatch/TypographySwatch.js +40 -0
  203. package/components/registry.d.ts +20 -0
  204. package/components/registry.js +7 -0
  205. package/components/registry.json +67 -0
  206. package/components/registry.json.d.ts +70 -0
  207. package/components/registry.json.js +8 -0
  208. package/fonts/MaterialSymbolsRounded-VariableFont_FILL,GRAD,opsz,wght.woff2 +0 -0
  209. package/fonts/material-symbols.css +94 -0
  210. package/index.d.ts +62 -0
  211. package/index.js +123 -0
  212. package/package.json +63 -0
  213. package/tokens/registry.d.ts +22 -0
  214. package/tokens/registry.js +14 -0
  215. package/tokens/registry.json +203 -0
  216. package/tokens/registry.json.d.ts +206 -0
  217. package/tokens/registry.json.js +8 -0
  218. package/tokens/tokens-dark.css +119 -0
  219. package/tokens/tokens-light.css +159 -0
  220. package/tokens/tokens-motion.css +68 -0
  221. package/tokens/tokens-primitives.css +176 -0
  222. package/tokens/tokens-typography.css +116 -0
  223. package/tokens/tokens.css +11 -0
@@ -0,0 +1,263 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useRef, useId, useSyncExternalStore, useCallback, useMemo, useEffect } from "react";
3
+ import ReactDOM from "react-dom";
4
+ import "./CommandPalette.css";
5
+ import "../../fonts/material-symbols.css";
6
+ const emptySubscribe = () => () => {
7
+ };
8
+ const CommandPalette = ({
9
+ open,
10
+ onOpenChange,
11
+ groups,
12
+ placeholder = "Type a command or search…",
13
+ emptyMessage = "No matching commands",
14
+ loading = false,
15
+ hotkey = true,
16
+ onSelect,
17
+ onSearchChange,
18
+ manualFiltering = false,
19
+ hideFooter = false,
20
+ className = ""
21
+ }) => {
22
+ const [query, setQuery] = useState("");
23
+ const [rawActiveIndex, setActiveIndex] = useState(0);
24
+ const [prevOpen, setPrevOpen] = useState(open);
25
+ const inputRef = useRef(null);
26
+ const listRef = useRef(null);
27
+ const previousFocusRef = useRef(null);
28
+ const generatedId = useId();
29
+ const baseClass = "ds-command-palette";
30
+ const mounted = useSyncExternalStore(emptySubscribe, () => true, () => false);
31
+ const matches = useCallback(
32
+ (command) => {
33
+ const q = query.trim().toLowerCase();
34
+ if (manualFiltering || q === "") return true;
35
+ const haystack = [command.label, command.description ?? "", ...command.keywords ?? []].join(" ").toLowerCase();
36
+ return haystack.includes(q);
37
+ },
38
+ [manualFiltering, query]
39
+ );
40
+ const filteredGroups = useMemo(
41
+ () => groups.map((group) => ({ ...group, commands: group.commands.filter(matches) })).filter((group) => group.commands.length > 0),
42
+ [groups, matches]
43
+ );
44
+ const flatCommands = useMemo(
45
+ () => filteredGroups.flatMap((g) => g.commands).filter((c) => !c.disabled),
46
+ [filteredGroups]
47
+ );
48
+ if (prevOpen !== open) {
49
+ setPrevOpen(open);
50
+ if (!open) {
51
+ setQuery("");
52
+ setActiveIndex(0);
53
+ }
54
+ }
55
+ const activeIndex = Math.min(rawActiveIndex, Math.max(0, flatCommands.length - 1));
56
+ useEffect(() => {
57
+ if (!hotkey) return;
58
+ const handleHotkey = (e) => {
59
+ if (e.key.toLowerCase() === "k" && (e.metaKey || e.ctrlKey)) {
60
+ e.preventDefault();
61
+ onOpenChange(!open);
62
+ }
63
+ };
64
+ document.addEventListener("keydown", handleHotkey);
65
+ return () => document.removeEventListener("keydown", handleHotkey);
66
+ }, [hotkey, open, onOpenChange]);
67
+ useEffect(() => {
68
+ if (open) {
69
+ previousFocusRef.current = document.activeElement;
70
+ const raf = requestAnimationFrame(() => inputRef.current?.focus());
71
+ return () => cancelAnimationFrame(raf);
72
+ }
73
+ if (previousFocusRef.current) {
74
+ previousFocusRef.current.focus();
75
+ previousFocusRef.current = null;
76
+ }
77
+ }, [open]);
78
+ useEffect(() => {
79
+ if (open) {
80
+ document.body.style.overflow = "hidden";
81
+ } else {
82
+ document.body.style.overflow = "";
83
+ }
84
+ return () => {
85
+ document.body.style.overflow = "";
86
+ };
87
+ }, [open]);
88
+ const runCommand = (command) => {
89
+ if (command.disabled) return;
90
+ command.onSelect?.();
91
+ onSelect?.(command);
92
+ onOpenChange(false);
93
+ };
94
+ const handleKeyDown = (e) => {
95
+ switch (e.key) {
96
+ case "ArrowDown":
97
+ e.preventDefault();
98
+ setActiveIndex(
99
+ (prev) => flatCommands.length === 0 ? 0 : (prev + 1) % flatCommands.length
100
+ );
101
+ break;
102
+ case "ArrowUp":
103
+ e.preventDefault();
104
+ setActiveIndex(
105
+ (prev) => flatCommands.length === 0 ? 0 : (prev - 1 + flatCommands.length) % flatCommands.length
106
+ );
107
+ break;
108
+ case "Home":
109
+ e.preventDefault();
110
+ setActiveIndex(0);
111
+ break;
112
+ case "End":
113
+ e.preventDefault();
114
+ setActiveIndex(Math.max(0, flatCommands.length - 1));
115
+ break;
116
+ case "Enter":
117
+ e.preventDefault();
118
+ if (flatCommands[activeIndex]) runCommand(flatCommands[activeIndex]);
119
+ break;
120
+ case "Escape":
121
+ e.preventDefault();
122
+ onOpenChange(false);
123
+ break;
124
+ }
125
+ };
126
+ useEffect(() => {
127
+ if (!open || !listRef.current) return;
128
+ const active = listRef.current.querySelector(
129
+ `[data-command-index="${activeIndex}"]`
130
+ );
131
+ active?.scrollIntoView({ block: "nearest" });
132
+ }, [activeIndex, open]);
133
+ const handleQueryChange = (next) => {
134
+ setQuery(next);
135
+ setActiveIndex(0);
136
+ onSearchChange?.(next);
137
+ };
138
+ const listboxId = `${generatedId}-listbox`;
139
+ const activeCommand = flatCommands[activeIndex];
140
+ const activeDescendantId = activeCommand ? `${generatedId}-command-${activeCommand.id}` : void 0;
141
+ const classes = [baseClass, open ? `${baseClass}--open` : "", className].filter(Boolean).join(" ");
142
+ let commandIndex = 0;
143
+ const palette = /* @__PURE__ */ jsxs("div", { className: classes, children: [
144
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__backdrop`, onClick: () => onOpenChange(false) }),
145
+ /* @__PURE__ */ jsxs(
146
+ "div",
147
+ {
148
+ className: `${baseClass}__panel`,
149
+ role: "dialog",
150
+ "aria-modal": "true",
151
+ "aria-label": "Command palette",
152
+ children: [
153
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__search`, children: [
154
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__search-icon material-symbols-rounded`, "aria-hidden": "true", children: "search" }),
155
+ /* @__PURE__ */ jsx(
156
+ "input",
157
+ {
158
+ ref: inputRef,
159
+ type: "text",
160
+ className: `${baseClass}__input`,
161
+ role: "combobox",
162
+ autoComplete: "off",
163
+ "aria-expanded": true,
164
+ "aria-controls": listboxId,
165
+ "aria-autocomplete": "list",
166
+ "aria-activedescendant": activeDescendantId,
167
+ "aria-label": placeholder,
168
+ placeholder,
169
+ value: query,
170
+ onChange: (e) => handleQueryChange(e.target.value),
171
+ onKeyDown: handleKeyDown
172
+ }
173
+ ),
174
+ /* @__PURE__ */ jsx(
175
+ "button",
176
+ {
177
+ type: "button",
178
+ className: `${baseClass}__close`,
179
+ onClick: () => onOpenChange(false),
180
+ "aria-label": "Close command palette",
181
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "close" })
182
+ }
183
+ )
184
+ ] }),
185
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__list`, role: "listbox", id: listboxId, ref: listRef, children: [
186
+ loading && /* @__PURE__ */ jsx("p", { className: `${baseClass}__status`, children: "Loading…" }),
187
+ !loading && filteredGroups.length === 0 && /* @__PURE__ */ jsx("p", { className: `${baseClass}__status`, children: emptyMessage }),
188
+ !loading && filteredGroups.map((group) => /* @__PURE__ */ jsxs(
189
+ "div",
190
+ {
191
+ className: `${baseClass}__group`,
192
+ role: "group",
193
+ "aria-label": group.label,
194
+ children: [
195
+ /* @__PURE__ */ jsx("p", { className: `${baseClass}__group-label`, children: group.label }),
196
+ group.commands.map((command) => {
197
+ const index = command.disabled ? -1 : commandIndex++;
198
+ const isActive = index === activeIndex;
199
+ return /* @__PURE__ */ jsxs(
200
+ "div",
201
+ {
202
+ id: `${generatedId}-command-${command.id}`,
203
+ "data-command-index": index,
204
+ className: [
205
+ `${baseClass}__command`,
206
+ isActive ? `${baseClass}__command--active` : "",
207
+ command.disabled ? `${baseClass}__command--disabled` : ""
208
+ ].filter(Boolean).join(" "),
209
+ role: "option",
210
+ "aria-selected": isActive,
211
+ "aria-disabled": command.disabled,
212
+ onMouseEnter: () => index >= 0 && setActiveIndex(index),
213
+ onMouseDown: (e) => e.preventDefault(),
214
+ onClick: () => runCommand(command),
215
+ children: [
216
+ command.icon && /* @__PURE__ */ jsx(
217
+ "span",
218
+ {
219
+ className: `${baseClass}__command-icon material-symbols-rounded`,
220
+ "aria-hidden": "true",
221
+ children: command.icon
222
+ }
223
+ ),
224
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__command-text`, children: [
225
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__command-label`, children: command.label }),
226
+ command.description && /* @__PURE__ */ jsx("span", { className: `${baseClass}__command-description`, children: command.description })
227
+ ] }),
228
+ command.shortcut && command.shortcut.length > 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__shortcut`, children: command.shortcut.map((key, i) => /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: key }, i)) })
229
+ ]
230
+ },
231
+ command.id
232
+ );
233
+ })
234
+ ]
235
+ },
236
+ group.label
237
+ ))
238
+ ] }),
239
+ !hideFooter && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__footer`, children: [
240
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__hint`, children: [
241
+ /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: "↑" }),
242
+ /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: "↓" }),
243
+ "to navigate"
244
+ ] }),
245
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__hint`, children: [
246
+ /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: "↵" }),
247
+ "to select"
248
+ ] }),
249
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__hint`, children: [
250
+ /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: "esc" }),
251
+ "to close"
252
+ ] })
253
+ ] })
254
+ ]
255
+ }
256
+ )
257
+ ] });
258
+ if (!mounted) return null;
259
+ return ReactDOM.createPortal(palette, document.body);
260
+ };
261
+ export {
262
+ CommandPalette
263
+ };
@@ -0,0 +1,123 @@
1
+ /* ============================================
2
+ CONTACT CARD
3
+ ============================================ */
4
+ .ds-contact-card {
5
+ display: flex;
6
+ align-items: center;
7
+ gap: var(--gap-lg);
8
+ padding: var(--padding-lg);
9
+ border: 1px solid var(--color-bg-container-border);
10
+ border-radius: var(--radius-md);
11
+ background: var(--color-bg-container-primary-semi);
12
+ text-decoration: none;
13
+ color: inherit;
14
+ transition: background var(--motion-duration-fast) var(--motion-ease-standard), border-color var(--motion-duration-fast) var(--motion-ease-standard);
15
+ /* Resets so the card renders identically whether it's an <a> or a <button>
16
+ (copy-only cards use a button). */
17
+ width: 100%;
18
+ box-sizing: border-box;
19
+ font: inherit;
20
+ text-align: left;
21
+ cursor: pointer;
22
+ }
23
+
24
+ .ds-contact-card:hover {
25
+ background: var(--color-bg-container-secondary);
26
+ border-color: var(--color-bg-container-border);
27
+ }
28
+
29
+ .ds-contact-card:focus-visible {
30
+ outline: 2px solid var(--color-action-primary-bg);
31
+ outline-offset: 2px;
32
+ }
33
+
34
+ /* Icon / logo slot */
35
+ .ds-contact-card__icon-wrap {
36
+ width: 24px;
37
+ height: 24px;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ flex-shrink: 0;
42
+ }
43
+
44
+ .ds-contact-card__icon {
45
+ --icon-size: var(--icon-size-md);
46
+ color: var(--color-text-secondary);
47
+ }
48
+
49
+ .ds-contact-card__logo {
50
+ width: 24px;
51
+ height: 24px;
52
+ object-fit: contain;
53
+ }
54
+
55
+ /* Text */
56
+ .ds-contact-card__text {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 2px;
60
+ flex: 1;
61
+ min-width: 0;
62
+ }
63
+
64
+ .ds-contact-card__label {
65
+ font-family: var(--font-title-body-family);
66
+ font-size: var(--font-title-body-size);
67
+ font-weight: var(--font-title-body-weight);
68
+ line-height: var(--font-title-body-line-height);
69
+ color: var(--color-text-primary);
70
+ }
71
+
72
+ .ds-contact-card__value {
73
+ font-family: var(--font-paragraph-sm-family);
74
+ font-size: var(--font-paragraph-sm-size);
75
+ font-weight: var(--font-paragraph-sm-weight);
76
+ line-height: var(--font-paragraph-sm-line-height);
77
+ color: var(--color-text-tertiary);
78
+ white-space: nowrap;
79
+ overflow: hidden;
80
+ text-overflow: ellipsis;
81
+ }
82
+
83
+ /* Actions */
84
+ .ds-contact-card__actions {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: var(--gap-xs);
88
+ flex-shrink: 0;
89
+ }
90
+
91
+ .ds-contact-card__copy-btn {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ width: 32px;
96
+ height: 32px;
97
+ border: none;
98
+ border-radius: var(--radius-full);
99
+ background: transparent;
100
+ color: var(--color-text-tertiary);
101
+ cursor: pointer;
102
+ transition: background var(--motion-duration-fast) var(--motion-ease-standard), color var(--motion-duration-fast) var(--motion-ease-standard);
103
+ }
104
+
105
+ .ds-contact-card__copy-btn:hover {
106
+ background: var(--color-bg-container-tertiary);
107
+ color: var(--color-text-primary);
108
+ }
109
+
110
+ .ds-contact-card__copy-btn:focus-visible {
111
+ outline: 2px solid var(--color-action-primary-bg);
112
+ outline-offset: 2px;
113
+ }
114
+
115
+ .ds-contact-card__copy-btn .material-symbols-rounded {
116
+ --icon-size: var(--icon-size-sm);
117
+ }
118
+
119
+ .ds-contact-card__chevron {
120
+ --icon-size: var(--icon-size-sm);
121
+ color: var(--color-text-tertiary);
122
+ flex-shrink: 0;
123
+ }
@@ -0,0 +1,23 @@
1
+ export interface ContactCardProps {
2
+ /** Primary label — e.g. "Email" */
3
+ label: string;
4
+ /** Visible value or description — e.g. "hello@example.com" */
5
+ value: string;
6
+ /** Link destination */
7
+ href: string;
8
+ /** Material Symbol name used when no logo is set */
9
+ icon?: string;
10
+ /** Path to a logo image — preferred over icon when both are provided */
11
+ logo?: string;
12
+ /** Opens link in a new tab and shows open_in_new indicator */
13
+ external?: boolean;
14
+ /** Renders a copy-to-clipboard button; fires onCopy(value) when clicked */
15
+ copyable?: boolean;
16
+ /** Called with the card's value when the copy button is clicked */
17
+ onCopy?: (value: string) => void;
18
+ /** Renders the whole card as a button that copies `value` on click — no navigation */
19
+ copyOnClick?: boolean;
20
+ /** Additional CSS classes */
21
+ className?: string;
22
+ }
23
+ export declare const ContactCard: ({ label, value, href, icon, logo, external, copyable, onCopy, copyOnClick, className, }: ContactCardProps) => import("react").JSX.Element;
@@ -0,0 +1,78 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import "./ContactCard.css";
3
+ import "../../fonts/material-symbols.css";
4
+ const ContactCard = ({
5
+ label,
6
+ value,
7
+ href,
8
+ icon,
9
+ logo,
10
+ external = false,
11
+ copyable = false,
12
+ onCopy,
13
+ copyOnClick = false,
14
+ className = ""
15
+ }) => {
16
+ const classes = ["ds-contact-card", className].filter(Boolean).join(" ");
17
+ const isProtocolLink = /^(mailto:|tel:)/i.test(href);
18
+ const openInNewTab = external && !isProtocolLink;
19
+ const trailingIcon = copyOnClick ? "content_copy" : external ? "open_in_new" : "arrow_forward";
20
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
21
+ /* @__PURE__ */ jsx("span", { className: "ds-contact-card__icon-wrap", "aria-hidden": "true", children: logo ? /* @__PURE__ */ jsx(
22
+ "img",
23
+ {
24
+ src: logo,
25
+ alt: "",
26
+ width: 24,
27
+ height: 24,
28
+ className: "ds-contact-card__logo"
29
+ }
30
+ ) : /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded ds-contact-card__icon", children: icon ?? "link" }) }),
31
+ /* @__PURE__ */ jsxs("div", { className: "ds-contact-card__text", children: [
32
+ /* @__PURE__ */ jsx("span", { className: "ds-contact-card__label", children: label }),
33
+ /* @__PURE__ */ jsx("span", { className: "ds-contact-card__value", children: value })
34
+ ] }),
35
+ /* @__PURE__ */ jsxs("div", { className: "ds-contact-card__actions", children: [
36
+ copyable && !copyOnClick && /* @__PURE__ */ jsx(
37
+ "button",
38
+ {
39
+ type: "button",
40
+ onClick: (e) => {
41
+ e.preventDefault();
42
+ e.stopPropagation();
43
+ onCopy?.(value);
44
+ },
45
+ className: "ds-contact-card__copy-btn",
46
+ "aria-label": `Copy ${value}`,
47
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "content_copy" })
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded ds-contact-card__chevron", "aria-hidden": "true", children: trailingIcon })
51
+ ] })
52
+ ] });
53
+ if (copyOnClick) {
54
+ return /* @__PURE__ */ jsx(
55
+ "button",
56
+ {
57
+ type: "button",
58
+ className: classes,
59
+ onClick: () => onCopy?.(value),
60
+ "aria-label": `Copy ${value} to clipboard`,
61
+ children: content
62
+ }
63
+ );
64
+ }
65
+ return /* @__PURE__ */ jsx(
66
+ "a",
67
+ {
68
+ href,
69
+ target: openInNewTab ? "_blank" : void 0,
70
+ rel: openInNewTab ? "noopener noreferrer" : void 0,
71
+ className: classes,
72
+ children: content
73
+ }
74
+ );
75
+ };
76
+ export {
77
+ ContactCard
78
+ };
@@ -0,0 +1,142 @@
1
+ /* ============================================
2
+ CONTRIBUTION GRAPH COMPONENT
3
+ GitHub-style activity heatmap — one cell per
4
+ day, coloured by the contribution colour ramp
5
+ ============================================ */
6
+
7
+ .ds-contribution-graph {
8
+ --ds-contribution-cell: 12px;
9
+ --ds-contribution-gap: 3px;
10
+
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: var(--gap-sm);
14
+ width: 100%;
15
+ min-width: 0;
16
+ }
17
+
18
+ /* Grid + month labels scroll together on narrow screens */
19
+ .ds-contribution-graph__scroll {
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: var(--gap-xxs);
23
+ overflow-x: auto;
24
+ padding-bottom: var(--padding-xxxs);
25
+ }
26
+
27
+ /* ============================================
28
+ MONTH LABELS
29
+ ============================================ */
30
+
31
+ .ds-contribution-graph__months {
32
+ display: grid;
33
+ column-gap: var(--ds-contribution-gap);
34
+ width: 100%;
35
+ }
36
+
37
+ .ds-contribution-graph__month-label {
38
+ grid-row: 1;
39
+ font-family: var(--font-paragraph-sm-family);
40
+ font-size: var(--font-paragraph-sm-size);
41
+ font-weight: var(--font-paragraph-sm-weight);
42
+ line-height: var(--font-paragraph-sm-line-height);
43
+ color: var(--color-text-tertiary);
44
+ white-space: nowrap;
45
+ }
46
+
47
+ /* ============================================
48
+ GRID
49
+ ============================================ */
50
+
51
+ .ds-contribution-graph__grid {
52
+ display: grid;
53
+ gap: var(--ds-contribution-gap);
54
+ width: 100%;
55
+ }
56
+
57
+ .ds-contribution-graph__week {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: var(--ds-contribution-gap);
61
+ }
62
+
63
+ .ds-contribution-graph__cell {
64
+ width: var(--ds-contribution-cell);
65
+ height: var(--ds-contribution-cell);
66
+ border-radius: var(--radius-xxs);
67
+ flex-shrink: 0;
68
+ }
69
+
70
+ /* Cells inside the grid stretch with their column; the fixed size above
71
+ only applies to the legend swatches */
72
+ .ds-contribution-graph__week .ds-contribution-graph__cell {
73
+ width: 100%;
74
+ height: auto;
75
+ aspect-ratio: 1 / 1;
76
+ }
77
+
78
+ .ds-contribution-graph__cell--empty {
79
+ background-color: transparent;
80
+ }
81
+
82
+ .ds-contribution-graph__cell--level-0 {
83
+ background-color: var(--color-chart-contribution-0);
84
+ }
85
+
86
+ .ds-contribution-graph__cell--level-1 {
87
+ background-color: var(--color-chart-contribution-1);
88
+ }
89
+
90
+ .ds-contribution-graph__cell--level-2 {
91
+ background-color: var(--color-chart-contribution-2);
92
+ }
93
+
94
+ .ds-contribution-graph__cell--level-3 {
95
+ background-color: var(--color-chart-contribution-3);
96
+ }
97
+
98
+ .ds-contribution-graph__cell--level-4 {
99
+ background-color: var(--color-chart-contribution-4);
100
+ }
101
+
102
+ /* ============================================
103
+ FOOTER — caption + legend
104
+ ============================================ */
105
+
106
+ .ds-contribution-graph__footer {
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: space-between;
110
+ gap: var(--gap-md);
111
+ flex-wrap: wrap;
112
+ }
113
+
114
+ .ds-contribution-graph__caption {
115
+ font-family: var(--font-paragraph-sm-family);
116
+ font-size: var(--font-paragraph-sm-size);
117
+ font-weight: var(--font-paragraph-sm-weight);
118
+ line-height: var(--font-paragraph-sm-line-height);
119
+ color: var(--color-text-secondary);
120
+ }
121
+
122
+ .ds-contribution-graph__legend {
123
+ display: flex;
124
+ align-items: center;
125
+ gap: var(--ds-contribution-gap);
126
+ }
127
+
128
+ .ds-contribution-graph__legend-label {
129
+ font-family: var(--font-paragraph-sm-family);
130
+ font-size: var(--font-paragraph-sm-size);
131
+ font-weight: var(--font-paragraph-sm-weight);
132
+ line-height: var(--font-paragraph-sm-line-height);
133
+ color: var(--color-text-tertiary);
134
+ }
135
+
136
+ .ds-contribution-graph__legend-label:first-child {
137
+ margin-right: var(--gap-xxs);
138
+ }
139
+
140
+ .ds-contribution-graph__legend-label:last-child {
141
+ margin-left: var(--gap-xxs);
142
+ }
@@ -0,0 +1,26 @@
1
+ export interface ContributionDay {
2
+ /** ISO date string, e.g. "2026-07-08" */
3
+ date: string;
4
+ /** Number of contributions on this day */
5
+ count: number;
6
+ /** Intensity level 0 (none) to 4 (most) — drives the cell colour */
7
+ level: 0 | 1 | 2 | 3 | 4;
8
+ }
9
+ export interface ContributionGraphProps {
10
+ /** One entry per day, ordered oldest to newest */
11
+ days: ContributionDay[];
12
+ /** Summary line shown under the grid, e.g. "496 contributions in the last year" */
13
+ caption?: string;
14
+ /** Show month labels above the grid */
15
+ showMonthLabels?: boolean;
16
+ /** Show the Less → More legend under the grid */
17
+ showLegend?: boolean;
18
+ /** Additional CSS classes */
19
+ className?: string;
20
+ }
21
+ /**
22
+ * ContributionGraph renders a GitHub-style contribution heatmap:
23
+ * one cell per day, arranged in weekday rows and week columns,
24
+ * coloured by activity level via the --color-chart-contribution-* ramp.
25
+ */
26
+ export declare const ContributionGraph: ({ days, caption, showMonthLabels, showLegend, className, }: ContributionGraphProps) => import("react").JSX.Element;