@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,170 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import "./DatePicker.css";
4
+ import "../../fonts/material-symbols.css";
5
+ const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
6
+ const MONTHS = [
7
+ "January",
8
+ "February",
9
+ "March",
10
+ "April",
11
+ "May",
12
+ "June",
13
+ "July",
14
+ "August",
15
+ "September",
16
+ "October",
17
+ "November",
18
+ "December"
19
+ ];
20
+ function parseDate(str) {
21
+ if (!str) return null;
22
+ const [y, m, d] = str.split("-").map(Number);
23
+ return new Date(y, m - 1, d);
24
+ }
25
+ function formatDate(date) {
26
+ const y = date.getFullYear();
27
+ const m = String(date.getMonth() + 1).padStart(2, "0");
28
+ const d = String(date.getDate()).padStart(2, "0");
29
+ return `${y}-${m}-${d}`;
30
+ }
31
+ function isSameDay(a, b) {
32
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
33
+ }
34
+ const DatePicker = ({
35
+ value,
36
+ size = "default",
37
+ disabled = false,
38
+ min,
39
+ max,
40
+ onDateSelect,
41
+ className = ""
42
+ }) => {
43
+ const selectedDate = parseDate(value);
44
+ const minDate = parseDate(min);
45
+ const maxDate = parseDate(max);
46
+ const today = useMemo(() => /* @__PURE__ */ new Date(), []);
47
+ const [viewMonth, setViewMonth] = useState(
48
+ selectedDate ? selectedDate.getMonth() : today.getMonth()
49
+ );
50
+ const [viewYear, setViewYear] = useState(
51
+ selectedDate ? selectedDate.getFullYear() : today.getFullYear()
52
+ );
53
+ const baseClass = "ds-date-picker";
54
+ const sizeClass = `${baseClass}--${size}`;
55
+ const disabledClass = disabled ? `${baseClass}--disabled` : "";
56
+ const classes = [baseClass, sizeClass, disabledClass, className].filter(Boolean).join(" ");
57
+ const firstDay = new Date(viewYear, viewMonth, 1).getDay();
58
+ const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
59
+ const daysInPrevMonth = new Date(viewYear, viewMonth, 0).getDate();
60
+ const cells = [];
61
+ for (let i = firstDay - 1; i >= 0; i--) {
62
+ const d = daysInPrevMonth - i;
63
+ cells.push({ day: d, inMonth: false, date: new Date(viewYear, viewMonth - 1, d) });
64
+ }
65
+ for (let d = 1; d <= daysInMonth; d++) {
66
+ cells.push({ day: d, inMonth: true, date: new Date(viewYear, viewMonth, d) });
67
+ }
68
+ const remaining = 42 - cells.length;
69
+ for (let d = 1; d <= remaining; d++) {
70
+ cells.push({ day: d, inMonth: false, date: new Date(viewYear, viewMonth + 1, d) });
71
+ }
72
+ const goToPrevMonth = () => {
73
+ if (viewMonth === 0) {
74
+ setViewMonth(11);
75
+ setViewYear(viewYear - 1);
76
+ } else {
77
+ setViewMonth(viewMonth - 1);
78
+ }
79
+ };
80
+ const goToNextMonth = () => {
81
+ if (viewMonth === 11) {
82
+ setViewMonth(0);
83
+ setViewYear(viewYear + 1);
84
+ } else {
85
+ setViewMonth(viewMonth + 1);
86
+ }
87
+ };
88
+ const isDateDisabled = (date) => {
89
+ if (disabled) return true;
90
+ if (minDate && date < minDate) return true;
91
+ if (maxDate && date > maxDate) return true;
92
+ return false;
93
+ };
94
+ const handleSelect = (cell) => {
95
+ if (isDateDisabled(cell.date)) return;
96
+ if (!cell.inMonth) {
97
+ setViewMonth(cell.date.getMonth());
98
+ setViewYear(cell.date.getFullYear());
99
+ }
100
+ onDateSelect?.(formatDate(cell.date));
101
+ };
102
+ return /* @__PURE__ */ jsxs("div", { className: classes, children: [
103
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
104
+ /* @__PURE__ */ jsx(
105
+ "button",
106
+ {
107
+ type: "button",
108
+ className: `${baseClass}__nav-btn material-symbols-rounded`,
109
+ onClick: goToPrevMonth,
110
+ disabled,
111
+ "aria-label": "Previous month",
112
+ children: "chevron_left"
113
+ }
114
+ ),
115
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__month-year`, children: [
116
+ MONTHS[viewMonth],
117
+ " ",
118
+ viewYear
119
+ ] }),
120
+ /* @__PURE__ */ jsx(
121
+ "button",
122
+ {
123
+ type: "button",
124
+ className: `${baseClass}__nav-btn material-symbols-rounded`,
125
+ onClick: goToNextMonth,
126
+ disabled,
127
+ "aria-label": "Next month",
128
+ children: "chevron_right"
129
+ }
130
+ )
131
+ ] }),
132
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__grid`, role: "grid", children: [
133
+ DAYS.map((day) => /* @__PURE__ */ jsx("div", { className: `${baseClass}__day-header`, role: "columnheader", children: day }, day)),
134
+ cells.map((cell, idx) => {
135
+ const isSelected = selectedDate && isSameDay(cell.date, selectedDate);
136
+ const isToday = isSameDay(cell.date, today);
137
+ const isCellDisabled = isDateDisabled(cell.date);
138
+ const cellClasses = [
139
+ `${baseClass}__cell`,
140
+ !cell.inMonth ? `${baseClass}__cell--outside` : "",
141
+ isSelected ? `${baseClass}__cell--selected` : "",
142
+ isToday && !isSelected ? `${baseClass}__cell--today` : "",
143
+ isCellDisabled ? `${baseClass}__cell--disabled` : ""
144
+ ].filter(Boolean).join(" ");
145
+ return /* @__PURE__ */ jsx(
146
+ "button",
147
+ {
148
+ type: "button",
149
+ className: cellClasses,
150
+ onClick: () => handleSelect(cell),
151
+ disabled: isCellDisabled,
152
+ "aria-label": cell.date.toLocaleDateString(void 0, {
153
+ weekday: "long",
154
+ year: "numeric",
155
+ month: "long",
156
+ day: "numeric"
157
+ }),
158
+ "aria-selected": isSelected || void 0,
159
+ role: "gridcell",
160
+ children: cell.day
161
+ },
162
+ idx
163
+ );
164
+ })
165
+ ] })
166
+ ] });
167
+ };
168
+ export {
169
+ DatePicker
170
+ };
@@ -0,0 +1,176 @@
1
+ /* ============================================
2
+ DIALOG COMPONENT
3
+ General-purpose modal with backdrop overlay
4
+ ============================================ */
5
+
6
+ .ds-dialog {
7
+ position: fixed;
8
+ inset: 0;
9
+ z-index: 100;
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ opacity: 0;
14
+ pointer-events: none;
15
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
16
+ }
17
+
18
+ .ds-dialog--open {
19
+ opacity: 1;
20
+ pointer-events: auto;
21
+ }
22
+
23
+ /* ============================================
24
+ BACKDROP
25
+ ============================================ */
26
+
27
+ .ds-dialog__backdrop {
28
+ position: absolute;
29
+ inset: 0;
30
+ background-color: var(--color-scrim);
31
+ }
32
+
33
+ /* ============================================
34
+ PANEL
35
+ ============================================ */
36
+
37
+ .ds-dialog__panel {
38
+ position: relative;
39
+ z-index: 1;
40
+ display: flex;
41
+ flex-direction: column;
42
+ gap: var(--gap-md); /* 16px */
43
+ width: 100%;
44
+ max-height: calc(100vh - var(--gap-xl) * 2); /* breathing room top/bottom */
45
+ padding: var(--padding-lg); /* 20px */
46
+ border: var(--border-xs) solid var(--color-bg-container-border);
47
+ border-radius: var(--radius-md); /* 12px */
48
+ background-color: var(--color-bg-page-primary);
49
+ box-shadow: var(--shadow-modal);
50
+ transform: scale(0.96);
51
+ transition: transform var(--motion-duration-base) var(--motion-ease-standard);
52
+ outline: none;
53
+ }
54
+
55
+ .ds-dialog--open .ds-dialog__panel {
56
+ transform: scale(1);
57
+ }
58
+
59
+ /* Sizes */
60
+ .ds-dialog--sm .ds-dialog__panel {
61
+ max-width: 400px;
62
+ }
63
+
64
+ .ds-dialog--md .ds-dialog__panel {
65
+ max-width: 560px;
66
+ }
67
+
68
+ .ds-dialog--lg .ds-dialog__panel {
69
+ max-width: 720px;
70
+ }
71
+
72
+ /* ============================================
73
+ HEADER
74
+ ============================================ */
75
+
76
+ .ds-dialog__header {
77
+ display: flex;
78
+ align-items: flex-start;
79
+ justify-content: space-between;
80
+ gap: var(--gap-md); /* 16px */
81
+ }
82
+
83
+ .ds-dialog__heading {
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: var(--gap-xs); /* 4px */
87
+ min-width: 0;
88
+ }
89
+
90
+ .ds-dialog__title {
91
+ font-family: var(--font-heading-6-family);
92
+ font-size: var(--font-heading-6-size);
93
+ font-weight: var(--font-heading-6-weight);
94
+ line-height: var(--font-heading-6-line-height);
95
+ letter-spacing: var(--font-heading-6-letter-spacing);
96
+ color: var(--color-text-primary);
97
+ margin: 0;
98
+ }
99
+
100
+ .ds-dialog__description {
101
+ font-family: var(--font-paragraph-sm-family);
102
+ font-size: var(--font-paragraph-sm-size);
103
+ font-weight: var(--font-paragraph-sm-weight);
104
+ line-height: var(--font-paragraph-sm-line-height);
105
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
106
+ color: var(--color-text-tertiary);
107
+ margin: 0;
108
+ }
109
+
110
+ /* Close button — ghost icon button */
111
+ .ds-dialog__close {
112
+ flex-shrink: 0;
113
+ display: inline-flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ width: 32px;
117
+ height: 32px;
118
+ padding: 0;
119
+ border: none;
120
+ border-radius: var(--radius-full); /* pill */
121
+ background-color: transparent;
122
+ cursor: pointer;
123
+ transition: background-color var(--motion-duration-base) var(--motion-ease-standard);
124
+ }
125
+
126
+ .ds-dialog__close:hover {
127
+ background-color: var(--color-action-passive-bg-hover);
128
+ }
129
+
130
+ .ds-dialog__close:focus-visible {
131
+ outline: 2px solid var(--color-action-primary-bg);
132
+ outline-offset: 2px;
133
+ }
134
+
135
+ .ds-dialog__close-icon {
136
+ --icon-size: var(--icon-size-sm);
137
+ line-height: 1;
138
+ color: var(--color-icon-primary);
139
+ }
140
+
141
+ /* ============================================
142
+ BODY
143
+ Scrolls when content exceeds the panel height
144
+ ============================================ */
145
+
146
+ .ds-dialog__body {
147
+ overflow-y: auto;
148
+ min-height: 0;
149
+ font-family: var(--font-paragraph-family);
150
+ font-size: var(--font-paragraph-size);
151
+ font-weight: var(--font-paragraph-weight);
152
+ line-height: var(--font-paragraph-line-height);
153
+ letter-spacing: var(--font-paragraph-letter-spacing);
154
+ color: var(--color-text-secondary);
155
+ }
156
+
157
+ /* ============================================
158
+ FOOTER
159
+ ============================================ */
160
+
161
+ .ds-dialog__footer {
162
+ display: flex;
163
+ justify-content: flex-end;
164
+ gap: var(--gap-sm); /* 8px */
165
+ padding-top: var(--padding-xs); /* 6px */
166
+ }
167
+
168
+ /* ============================================
169
+ RESPONSIVE
170
+ ============================================ */
171
+
172
+ @media (max-width: 480px) {
173
+ .ds-dialog__panel {
174
+ margin: var(--padding-md);
175
+ }
176
+ }
@@ -0,0 +1,28 @@
1
+ import { default as React } from 'react';
2
+ export interface DialogProps {
3
+ /** Whether the dialog is open */
4
+ open: boolean;
5
+ /** Callback when dialog requests to close */
6
+ onOpenChange: (open: boolean) => void;
7
+ /** Dialog title */
8
+ title: string;
9
+ /** Optional subtitle under the title */
10
+ description?: string;
11
+ /** Dialog body content */
12
+ children?: React.ReactNode;
13
+ /** Optional footer slot — typically a row of Buttons */
14
+ footer?: React.ReactNode;
15
+ /** Panel width */
16
+ size?: 'sm' | 'md' | 'lg';
17
+ /** Whether ESC, backdrop click, and the close button can dismiss */
18
+ dismissible?: boolean;
19
+ /** Additional CSS classes */
20
+ className?: string;
21
+ }
22
+ /**
23
+ * Dialog component — a general-purpose modal with arbitrary
24
+ * content and an optional footer. Traps focus, restores it on
25
+ * close, locks body scroll, and supports ESC / backdrop / close
26
+ * button dismissal. For confirm/cancel prompts use AlertDialog.
27
+ */
28
+ export declare const Dialog: ({ open, onOpenChange, title, description, children, footer, size, dismissible, className, }: DialogProps) => React.ReactPortal | null;
@@ -0,0 +1,132 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useRef, useId, useSyncExternalStore, useEffect } from "react";
3
+ import ReactDOM from "react-dom";
4
+ import "./Dialog.css";
5
+ import "../../fonts/material-symbols.css";
6
+ const emptySubscribe = () => () => {
7
+ };
8
+ const Dialog = ({
9
+ open,
10
+ onOpenChange,
11
+ title,
12
+ description,
13
+ children,
14
+ footer,
15
+ size = "md",
16
+ dismissible = true,
17
+ className = ""
18
+ }) => {
19
+ const panelRef = useRef(null);
20
+ const previousFocusRef = useRef(null);
21
+ const titleId = useId();
22
+ const descId = useId();
23
+ const baseClass = "ds-dialog";
24
+ const mounted = useSyncExternalStore(emptySubscribe, () => true, () => false);
25
+ useEffect(() => {
26
+ if (open) {
27
+ previousFocusRef.current = document.activeElement;
28
+ }
29
+ }, [open]);
30
+ useEffect(() => {
31
+ if (!open) return;
32
+ const panel = panelRef.current;
33
+ if (panel) {
34
+ const first = panel.querySelector(
35
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
36
+ );
37
+ (first ?? panel).focus();
38
+ }
39
+ const handleKeyDown = (e) => {
40
+ if (e.key === "Escape" && dismissible) {
41
+ onOpenChange(false);
42
+ return;
43
+ }
44
+ if (e.key === "Tab" && panel) {
45
+ const focusable = panel.querySelectorAll(
46
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
47
+ );
48
+ if (focusable.length === 0) return;
49
+ const first = focusable[0];
50
+ const last = focusable[focusable.length - 1];
51
+ if (e.shiftKey && document.activeElement === first) {
52
+ e.preventDefault();
53
+ last.focus();
54
+ } else if (!e.shiftKey && document.activeElement === last) {
55
+ e.preventDefault();
56
+ first.focus();
57
+ }
58
+ }
59
+ };
60
+ document.addEventListener("keydown", handleKeyDown);
61
+ return () => document.removeEventListener("keydown", handleKeyDown);
62
+ }, [open, dismissible, onOpenChange]);
63
+ useEffect(() => {
64
+ if (!open && previousFocusRef.current) {
65
+ previousFocusRef.current.focus();
66
+ previousFocusRef.current = null;
67
+ }
68
+ }, [open]);
69
+ useEffect(() => {
70
+ if (open) {
71
+ document.body.style.overflow = "hidden";
72
+ } else {
73
+ document.body.style.overflow = "";
74
+ }
75
+ return () => {
76
+ document.body.style.overflow = "";
77
+ };
78
+ }, [open]);
79
+ const handleBackdropClick = () => {
80
+ if (dismissible) onOpenChange(false);
81
+ };
82
+ const openClass = open ? `${baseClass}--open` : "";
83
+ const sizeClass = `${baseClass}--${size}`;
84
+ const containerClasses = [baseClass, sizeClass, openClass, className].filter(Boolean).join(" ");
85
+ const dialog = /* @__PURE__ */ jsxs("div", { className: containerClasses, children: [
86
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__backdrop`, onClick: handleBackdropClick }),
87
+ /* @__PURE__ */ jsxs(
88
+ "div",
89
+ {
90
+ className: `${baseClass}__panel`,
91
+ ref: panelRef,
92
+ role: "dialog",
93
+ "aria-modal": "true",
94
+ "aria-labelledby": titleId,
95
+ "aria-describedby": description ? descId : void 0,
96
+ tabIndex: -1,
97
+ children: [
98
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
99
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
100
+ /* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, id: titleId, children: title }),
101
+ description && /* @__PURE__ */ jsx("p", { className: `${baseClass}__description`, id: descId, children: description })
102
+ ] }),
103
+ dismissible && /* @__PURE__ */ jsx(
104
+ "button",
105
+ {
106
+ type: "button",
107
+ className: `${baseClass}__close`,
108
+ onClick: () => onOpenChange(false),
109
+ "aria-label": "Close dialog",
110
+ children: /* @__PURE__ */ jsx(
111
+ "span",
112
+ {
113
+ className: `${baseClass}__close-icon material-symbols-rounded`,
114
+ "aria-hidden": "true",
115
+ children: "close"
116
+ }
117
+ )
118
+ }
119
+ )
120
+ ] }),
121
+ children && /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children }),
122
+ footer && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer`, children: footer })
123
+ ]
124
+ }
125
+ )
126
+ ] });
127
+ if (!mounted) return null;
128
+ return ReactDOM.createPortal(dialog, document.body);
129
+ };
130
+ export {
131
+ Dialog
132
+ };
@@ -0,0 +1,100 @@
1
+ /* ============================================
2
+ DIVIDER COMPONENT
3
+ Thin rule separating stacked content
4
+ ============================================ */
5
+
6
+ /* Base — plain horizontal <hr> */
7
+ .ds-divider {
8
+ border: none;
9
+ margin: 0;
10
+ border-top: var(--border-xs) solid var(--color-divider);
11
+ width: 100%;
12
+ }
13
+
14
+ /* ============================================
15
+ SPACING
16
+ Block margin for horizontal, inline for vertical
17
+ ============================================ */
18
+
19
+ .ds-divider--spacing-none {
20
+ margin-block: 0;
21
+ }
22
+
23
+ .ds-divider--spacing-sm {
24
+ margin-block: var(--gap-sm); /* 8px */
25
+ }
26
+
27
+ .ds-divider--spacing-md {
28
+ margin-block: var(--gap-md); /* 16px */
29
+ }
30
+
31
+ .ds-divider--spacing-lg {
32
+ margin-block: var(--gap-lg); /* 20px */
33
+ }
34
+
35
+ /* ============================================
36
+ LABELED
37
+ Text label inline with the rule
38
+ ============================================ */
39
+
40
+ .ds-divider--labeled {
41
+ border-top: none;
42
+ display: flex;
43
+ align-items: center;
44
+ gap: var(--gap-sm-md); /* 12px */
45
+ }
46
+
47
+ .ds-divider--labeled::before,
48
+ .ds-divider--labeled::after {
49
+ content: '';
50
+ flex: 1;
51
+ height: var(--border-xs);
52
+ background-color: var(--color-divider);
53
+ }
54
+
55
+ /* Label at the start — no leading segment */
56
+ .ds-divider--label-start::before {
57
+ display: none;
58
+ }
59
+
60
+ .ds-divider__label {
61
+ flex-shrink: 0;
62
+ font-family: var(--font-paragraph-sm-family);
63
+ font-size: var(--font-paragraph-sm-size);
64
+ font-weight: var(--font-paragraph-sm-weight);
65
+ line-height: var(--font-paragraph-sm-line-height);
66
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
67
+ color: var(--color-text-secondary);
68
+ }
69
+
70
+ /* ============================================
71
+ VERTICAL
72
+ Stretches to container height inside flex rows
73
+ ============================================ */
74
+
75
+ .ds-divider--vertical {
76
+ border-top: none;
77
+ width: var(--border-xs);
78
+ align-self: stretch;
79
+ background-color: var(--color-divider);
80
+ }
81
+
82
+ .ds-divider--vertical.ds-divider--spacing-none {
83
+ margin-block: 0;
84
+ margin-inline: 0;
85
+ }
86
+
87
+ .ds-divider--vertical.ds-divider--spacing-sm {
88
+ margin-block: 0;
89
+ margin-inline: var(--gap-sm); /* 8px */
90
+ }
91
+
92
+ .ds-divider--vertical.ds-divider--spacing-md {
93
+ margin-block: 0;
94
+ margin-inline: var(--gap-md); /* 16px */
95
+ }
96
+
97
+ .ds-divider--vertical.ds-divider--spacing-lg {
98
+ margin-block: 0;
99
+ margin-inline: var(--gap-lg); /* 20px */
100
+ }
@@ -0,0 +1,18 @@
1
+ export interface DividerProps {
2
+ /** Divider direction */
3
+ orientation?: 'horizontal' | 'vertical';
4
+ /** Optional label rendered inline in the line (horizontal only) */
5
+ label?: string;
6
+ /** Label placement along the line */
7
+ labelPosition?: 'start' | 'center';
8
+ /** Margin around the divider (block for horizontal, inline for vertical) */
9
+ spacing?: 'none' | 'sm' | 'md' | 'lg';
10
+ /** Additional CSS classes */
11
+ className?: string;
12
+ }
13
+ /**
14
+ * Divider component — a thin rule separating stacked content.
15
+ * Plain horizontal by default; supports an inline text label
16
+ * and a vertical orientation for use inside flex rows.
17
+ */
18
+ export declare const Divider: ({ orientation, label, labelPosition, spacing, className, }: DividerProps) => import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import "./Divider.css";
3
+ const Divider = ({
4
+ orientation = "horizontal",
5
+ label,
6
+ labelPosition = "center",
7
+ spacing = "md",
8
+ className = ""
9
+ }) => {
10
+ const baseClass = "ds-divider";
11
+ const spacingClass = `${baseClass}--spacing-${spacing}`;
12
+ if (orientation === "vertical") {
13
+ const classes2 = [baseClass, `${baseClass}--vertical`, spacingClass, className].filter(Boolean).join(" ");
14
+ return /* @__PURE__ */ jsx("div", { className: classes2, role: "separator", "aria-orientation": "vertical" });
15
+ }
16
+ if (label) {
17
+ const classes2 = [
18
+ baseClass,
19
+ `${baseClass}--labeled`,
20
+ `${baseClass}--label-${labelPosition}`,
21
+ spacingClass,
22
+ className
23
+ ].filter(Boolean).join(" ");
24
+ return /* @__PURE__ */ jsx("div", { className: classes2, role: "separator", "aria-orientation": "horizontal", children: /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label }) });
25
+ }
26
+ const classes = [baseClass, spacingClass, className].filter(Boolean).join(" ");
27
+ return /* @__PURE__ */ jsx("hr", { className: classes });
28
+ };
29
+ export {
30
+ Divider
31
+ };