@sixthshift/design-system 0.2.1 → 0.3.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 (288) hide show
  1. package/README.md +135 -28
  2. package/dist/components/Badge/Badge.d.ts +5 -2
  3. package/dist/components/Badge/Badge.d.ts.map +1 -1
  4. package/dist/components/Badge/Badge.js +5 -3
  5. package/dist/components/Badge/Badge.js.map +1 -1
  6. package/dist/components/BarChart/BarChart.d.ts +8 -2
  7. package/dist/components/BarChart/BarChart.d.ts.map +1 -1
  8. package/dist/components/BarChart/BarChart.js +5 -3
  9. package/dist/components/BarChart/BarChart.js.map +1 -1
  10. package/dist/components/Calendar/Calendar.d.ts +17 -1
  11. package/dist/components/Calendar/Calendar.d.ts.map +1 -1
  12. package/dist/components/Calendar/Calendar.js +80 -225
  13. package/dist/components/Calendar/Calendar.js.map +1 -1
  14. package/dist/components/Calendar/CalendarView.d.ts +15 -0
  15. package/dist/components/Calendar/CalendarView.d.ts.map +1 -0
  16. package/dist/components/Calendar/CalendarView.js +255 -0
  17. package/dist/components/Calendar/CalendarView.js.map +1 -0
  18. package/dist/components/Calendar/calendar.types.d.ts +78 -27
  19. package/dist/components/Calendar/calendar.types.d.ts.map +1 -1
  20. package/dist/components/Calendar/calendar.types.js +10 -3
  21. package/dist/components/Calendar/calendar.types.js.map +1 -1
  22. package/dist/components/Calendar/index.d.ts +16 -2
  23. package/dist/components/Calendar/index.d.ts.map +1 -1
  24. package/dist/components/Calendar/index.js +15 -1
  25. package/dist/components/Calendar/index.js.map +1 -1
  26. package/dist/components/ColorDot/ColorDot.d.ts +9 -3
  27. package/dist/components/ColorDot/ColorDot.d.ts.map +1 -1
  28. package/dist/components/ColorDot/ColorDot.js +5 -3
  29. package/dist/components/ColorDot/ColorDot.js.map +1 -1
  30. package/dist/components/DatePicker/DatePicker.d.ts +2 -2
  31. package/dist/components/DatePicker/DatePicker.d.ts.map +1 -1
  32. package/dist/components/DatePicker/DatePicker.js +65 -10
  33. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  34. package/dist/components/DatePicker/datepicker.types.d.ts +34 -36
  35. package/dist/components/DatePicker/datepicker.types.d.ts.map +1 -1
  36. package/dist/components/DatePicker/datepicker.types.js +5 -3
  37. package/dist/components/DatePicker/datepicker.types.js.map +1 -1
  38. package/dist/components/DatePicker/index.d.ts +10 -1
  39. package/dist/components/DatePicker/index.d.ts.map +1 -1
  40. package/dist/components/DatePicker/index.js +8 -0
  41. package/dist/components/DatePicker/index.js.map +1 -1
  42. package/dist/components/DateRangePicker/DateRangePicker.d.ts +3 -2
  43. package/dist/components/DateRangePicker/DateRangePicker.d.ts.map +1 -1
  44. package/dist/components/DateRangePicker/DateRangePicker.js +17 -21
  45. package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
  46. package/dist/components/DateRangePicker/daterangepicker.types.d.ts +20 -20
  47. package/dist/components/DateRangePicker/daterangepicker.types.d.ts.map +1 -1
  48. package/dist/components/DateRangePicker/daterangepicker.types.js +6 -0
  49. package/dist/components/DateRangePicker/daterangepicker.types.js.map +1 -1
  50. package/dist/components/DateRangePicker/index.d.ts +8 -1
  51. package/dist/components/DateRangePicker/index.d.ts.map +1 -1
  52. package/dist/components/DateRangePicker/index.js +6 -0
  53. package/dist/components/DateRangePicker/index.js.map +1 -1
  54. package/dist/components/DateTimePicker/DateTimePicker.d.ts +3 -3
  55. package/dist/components/DateTimePicker/DateTimePicker.d.ts.map +1 -1
  56. package/dist/components/DateTimePicker/DateTimePicker.js +30 -12
  57. package/dist/components/DateTimePicker/DateTimePicker.js.map +1 -1
  58. package/dist/components/DateTimePicker/datetimepicker.types.d.ts +20 -17
  59. package/dist/components/DateTimePicker/datetimepicker.types.d.ts.map +1 -1
  60. package/dist/components/DateTimePicker/datetimepicker.types.js +10 -0
  61. package/dist/components/DateTimePicker/datetimepicker.types.js.map +1 -1
  62. package/dist/components/DateTimePicker/index.d.ts +8 -1
  63. package/dist/components/DateTimePicker/index.d.ts.map +1 -1
  64. package/dist/components/DateTimePicker/index.js +6 -0
  65. package/dist/components/DateTimePicker/index.js.map +1 -1
  66. package/dist/components/DateTimeRangePicker/DateTimeRangePicker.d.ts +3 -3
  67. package/dist/components/DateTimeRangePicker/DateTimeRangePicker.d.ts.map +1 -1
  68. package/dist/components/DateTimeRangePicker/DateTimeRangePicker.js +41 -20
  69. package/dist/components/DateTimeRangePicker/DateTimeRangePicker.js.map +1 -1
  70. package/dist/components/DateTimeRangePicker/datetimerangepicker.types.d.ts +22 -18
  71. package/dist/components/DateTimeRangePicker/datetimerangepicker.types.d.ts.map +1 -1
  72. package/dist/components/DateTimeRangePicker/datetimerangepicker.types.js +8 -0
  73. package/dist/components/DateTimeRangePicker/datetimerangepicker.types.js.map +1 -1
  74. package/dist/components/DateTimeRangePicker/index.d.ts +8 -0
  75. package/dist/components/DateTimeRangePicker/index.d.ts.map +1 -1
  76. package/dist/components/DateTimeRangePicker/index.js +7 -0
  77. package/dist/components/DateTimeRangePicker/index.js.map +1 -1
  78. package/dist/components/EmptyState/EmptyState.d.ts +16 -5
  79. package/dist/components/EmptyState/EmptyState.d.ts.map +1 -1
  80. package/dist/components/EmptyState/EmptyState.js +5 -3
  81. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  82. package/dist/components/Field/Field.d.ts +3 -3
  83. package/dist/components/Field/Field.d.ts.map +1 -1
  84. package/dist/components/Field/Field.js +6 -4
  85. package/dist/components/Field/Field.js.map +1 -1
  86. package/dist/components/LineChart/LineChart.d.ts +18 -2
  87. package/dist/components/LineChart/LineChart.d.ts.map +1 -1
  88. package/dist/components/LineChart/LineChart.js +5 -3
  89. package/dist/components/LineChart/LineChart.js.map +1 -1
  90. package/dist/components/Markdown/Markdown.d.ts +2 -4
  91. package/dist/components/Markdown/Markdown.d.ts.map +1 -1
  92. package/dist/components/Markdown/Markdown.js +3 -2
  93. package/dist/components/Markdown/Markdown.js.map +1 -1
  94. package/dist/components/Message/Message.d.ts.map +1 -1
  95. package/dist/components/Message/Message.js +16 -1
  96. package/dist/components/Message/Message.js.map +1 -1
  97. package/dist/components/MetricList/MetricList.d.ts +3 -3
  98. package/dist/components/MetricList/MetricList.d.ts.map +1 -1
  99. package/dist/components/MetricList/MetricList.js +5 -3
  100. package/dist/components/MetricList/MetricList.js.map +1 -1
  101. package/dist/components/Modal/Modal.d.ts +24 -5
  102. package/dist/components/Modal/Modal.d.ts.map +1 -1
  103. package/dist/components/Modal/Modal.js +16 -6
  104. package/dist/components/Modal/Modal.js.map +1 -1
  105. package/dist/components/Modal/components/ModalContext.d.ts +14 -0
  106. package/dist/components/Modal/components/ModalContext.d.ts.map +1 -1
  107. package/dist/components/Modal/components/ModalContext.js.map +1 -1
  108. package/dist/components/Modal/components/ModalHeader.d.ts.map +1 -1
  109. package/dist/components/Modal/components/ModalHeader.js +13 -2
  110. package/dist/components/Modal/components/ModalHeader.js.map +1 -1
  111. package/dist/components/NavBottom/NavBottom.d.ts +2 -1
  112. package/dist/components/NavBottom/NavBottom.d.ts.map +1 -1
  113. package/dist/components/NavBottom/NavBottom.js +6 -4
  114. package/dist/components/NavBottom/NavBottom.js.map +1 -1
  115. package/dist/components/NavSide/NavItem.js +1 -1
  116. package/dist/components/NavSide/NavItem.js.map +1 -1
  117. package/dist/components/NavSide/NavSide.d.ts +2 -1
  118. package/dist/components/NavSide/NavSide.d.ts.map +1 -1
  119. package/dist/components/NavSide/NavSide.js +5 -3
  120. package/dist/components/NavSide/NavSide.js.map +1 -1
  121. package/dist/components/Popover/Popover.d.ts.map +1 -1
  122. package/dist/components/Popover/Popover.js +7 -0
  123. package/dist/components/Popover/Popover.js.map +1 -1
  124. package/dist/components/Popover/components/PopoverBody.d.ts.map +1 -1
  125. package/dist/components/Popover/components/PopoverBody.js +5 -2
  126. package/dist/components/Popover/components/PopoverBody.js.map +1 -1
  127. package/dist/components/Popover/components/PopoverContext.d.ts +1 -0
  128. package/dist/components/Popover/components/PopoverContext.d.ts.map +1 -1
  129. package/dist/components/Popover/components/PopoverContext.js.map +1 -1
  130. package/dist/components/Popover/components/PopoverTrigger.d.ts.map +1 -1
  131. package/dist/components/Popover/components/PopoverTrigger.js +2 -2
  132. package/dist/components/Popover/components/PopoverTrigger.js.map +1 -1
  133. package/dist/components/ProgressBar/ProgressBar.d.ts +2 -1
  134. package/dist/components/ProgressBar/ProgressBar.d.ts.map +1 -1
  135. package/dist/components/ProgressBar/ProgressBar.js +5 -3
  136. package/dist/components/ProgressBar/ProgressBar.js.map +1 -1
  137. package/dist/components/Select/Select.d.ts +11 -2
  138. package/dist/components/Select/Select.d.ts.map +1 -1
  139. package/dist/components/Select/Select.js +64 -18
  140. package/dist/components/Select/Select.js.map +1 -1
  141. package/dist/components/Select/SelectDropdown.d.ts +14 -2
  142. package/dist/components/Select/SelectDropdown.d.ts.map +1 -1
  143. package/dist/components/Select/SelectDropdown.js +19 -4
  144. package/dist/components/Select/SelectDropdown.js.map +1 -1
  145. package/dist/components/Select/SelectTrigger.d.ts +10 -3
  146. package/dist/components/Select/SelectTrigger.d.ts.map +1 -1
  147. package/dist/components/Select/SelectTrigger.js +12 -3
  148. package/dist/components/Select/SelectTrigger.js.map +1 -1
  149. package/dist/components/Select/useSelectKeyboard.d.ts +15 -1
  150. package/dist/components/Select/useSelectKeyboard.d.ts.map +1 -1
  151. package/dist/components/Select/useSelectKeyboard.js +73 -65
  152. package/dist/components/Select/useSelectKeyboard.js.map +1 -1
  153. package/dist/components/Sheet/Sheet.d.ts +19 -5
  154. package/dist/components/Sheet/Sheet.d.ts.map +1 -1
  155. package/dist/components/Sheet/Sheet.js +18 -6
  156. package/dist/components/Sheet/Sheet.js.map +1 -1
  157. package/dist/components/Sheet/components/SheetContext.d.ts +10 -0
  158. package/dist/components/Sheet/components/SheetContext.d.ts.map +1 -1
  159. package/dist/components/Sheet/components/SheetContext.js.map +1 -1
  160. package/dist/components/Sheet/components/SheetHeader.d.ts.map +1 -1
  161. package/dist/components/Sheet/components/SheetHeader.js +11 -2
  162. package/dist/components/Sheet/components/SheetHeader.js.map +1 -1
  163. package/dist/components/Skeleton/Skeleton.d.ts +2 -1
  164. package/dist/components/Skeleton/Skeleton.d.ts.map +1 -1
  165. package/dist/components/Skeleton/Skeleton.js +5 -3
  166. package/dist/components/Skeleton/Skeleton.js.map +1 -1
  167. package/dist/components/Sparkline/Sparkline.d.ts +10 -2
  168. package/dist/components/Sparkline/Sparkline.d.ts.map +1 -1
  169. package/dist/components/Sparkline/Sparkline.js +5 -3
  170. package/dist/components/Sparkline/Sparkline.js.map +1 -1
  171. package/dist/components/StatsCard/StatsCard.d.ts +4 -4
  172. package/dist/components/StatsCard/StatsCard.d.ts.map +1 -1
  173. package/dist/components/StatsCard/StatsCard.js +5 -3
  174. package/dist/components/StatsCard/StatsCard.js.map +1 -1
  175. package/dist/components/Tabs/Tabs.d.ts +2 -2
  176. package/dist/components/Tabs/components/TabsList.d.ts +1 -1
  177. package/dist/components/Tabs/components/TabsList.d.ts.map +1 -1
  178. package/dist/components/Tabs/components/TabsList.js +5 -4
  179. package/dist/components/Tabs/components/TabsList.js.map +1 -1
  180. package/dist/components/Tabs/components/TabsPanels.d.ts +1 -1
  181. package/dist/components/Tabs/components/TabsPanels.d.ts.map +1 -1
  182. package/dist/components/Tabs/components/TabsPanels.js +5 -3
  183. package/dist/components/Tabs/components/TabsPanels.js.map +1 -1
  184. package/dist/components/TagChip/TagChip.d.ts +2 -1
  185. package/dist/components/TagChip/TagChip.d.ts.map +1 -1
  186. package/dist/components/TagChip/TagChip.js +5 -3
  187. package/dist/components/TagChip/TagChip.js.map +1 -1
  188. package/dist/components/TagInput/TagInput.d.ts +1 -1
  189. package/dist/components/TagInput/TagInput.d.ts.map +1 -1
  190. package/dist/components/TagInput/TagInput.js +5 -4
  191. package/dist/components/TagInput/TagInput.js.map +1 -1
  192. package/dist/components/TimePicker/TimePicker.d.ts +2 -2
  193. package/dist/components/TimePicker/TimePicker.d.ts.map +1 -1
  194. package/dist/components/TimePicker/TimePicker.js +27 -11
  195. package/dist/components/TimePicker/TimePicker.js.map +1 -1
  196. package/dist/components/TimePicker/timepicker.types.d.ts +14 -13
  197. package/dist/components/TimePicker/timepicker.types.d.ts.map +1 -1
  198. package/dist/components/TimePicker/timepicker.types.js +4 -3
  199. package/dist/components/TimePicker/timepicker.types.js.map +1 -1
  200. package/dist/date-time/index.d.ts +2 -0
  201. package/dist/date-time/index.d.ts.map +1 -1
  202. package/dist/date-time/index.js +9 -0
  203. package/dist/date-time/index.js.map +1 -1
  204. package/dist/date-time/iso.d.ts +261 -0
  205. package/dist/date-time/iso.d.ts.map +1 -0
  206. package/dist/date-time/iso.js +358 -0
  207. package/dist/date-time/iso.js.map +1 -0
  208. package/dist/date-time/now.d.ts +7 -6
  209. package/dist/date-time/now.d.ts.map +1 -1
  210. package/dist/date-time/now.js +3 -3
  211. package/dist/date-time/now.js.map +1 -1
  212. package/dist/theme/index.d.ts +4 -3
  213. package/dist/theme/index.d.ts.map +1 -1
  214. package/dist/theme/index.js +3 -2
  215. package/dist/theme/index.js.map +1 -1
  216. package/dist/theme/schema.d.ts +70 -32
  217. package/dist/theme/schema.d.ts.map +1 -1
  218. package/dist/theme/schema.js +36 -18
  219. package/dist/theme/schema.js.map +1 -1
  220. package/package.json +9 -15
  221. package/src/components/Badge/Badge.tsx +5 -4
  222. package/src/components/BarChart/BarChart.tsx +25 -30
  223. package/src/components/Calendar/Calendar.tsx +91 -401
  224. package/src/components/Calendar/CalendarView.tsx +445 -0
  225. package/src/components/Calendar/calendar.types.ts +94 -29
  226. package/src/components/Calendar/index.ts +16 -17
  227. package/src/components/ColorDot/ColorDot.tsx +6 -4
  228. package/src/components/DatePicker/DatePicker.tsx +92 -16
  229. package/src/components/DatePicker/datepicker.types.ts +38 -39
  230. package/src/components/DatePicker/index.ts +11 -10
  231. package/src/components/DateRangePicker/DateRangePicker.tsx +17 -21
  232. package/src/components/DateRangePicker/daterangepicker.types.ts +22 -22
  233. package/src/components/DateRangePicker/index.ts +9 -1
  234. package/src/components/DateTimePicker/DateTimePicker.tsx +46 -18
  235. package/src/components/DateTimePicker/datetimepicker.types.ts +22 -18
  236. package/src/components/DateTimePicker/index.ts +9 -1
  237. package/src/components/DateTimeRangePicker/DateTimeRangePicker.tsx +62 -28
  238. package/src/components/DateTimeRangePicker/datetimerangepicker.types.ts +24 -19
  239. package/src/components/DateTimeRangePicker/index.ts +9 -0
  240. package/src/components/EmptyState/EmptyState.tsx +8 -7
  241. package/src/components/Field/Field.tsx +7 -6
  242. package/src/components/LineChart/LineChart.tsx +147 -141
  243. package/src/components/Markdown/Markdown.tsx +4 -3
  244. package/src/components/Message/Message.tsx +17 -1
  245. package/src/components/MetricList/MetricList.tsx +10 -5
  246. package/src/components/Modal/Modal.tsx +119 -87
  247. package/src/components/Modal/components/ModalContext.tsx +14 -0
  248. package/src/components/Modal/components/ModalHeader.tsx +14 -2
  249. package/src/components/NavBottom/NavBottom.tsx +6 -4
  250. package/src/components/NavSide/NavItem.tsx +1 -1
  251. package/src/components/NavSide/NavSide.tsx +5 -3
  252. package/src/components/Popover/Popover.tsx +7 -0
  253. package/src/components/Popover/components/PopoverBody.tsx +6 -1
  254. package/src/components/Popover/components/PopoverContext.tsx +1 -0
  255. package/src/components/Popover/components/PopoverTrigger.tsx +2 -1
  256. package/src/components/ProgressBar/ProgressBar.tsx +32 -28
  257. package/src/components/Select/Select.tsx +76 -19
  258. package/src/components/Select/SelectDropdown.tsx +32 -3
  259. package/src/components/Select/SelectTrigger.tsx +26 -3
  260. package/src/components/Select/useSelectKeyboard.ts +20 -10
  261. package/src/components/Sheet/Sheet.tsx +108 -83
  262. package/src/components/Sheet/components/SheetContext.tsx +10 -0
  263. package/src/components/Sheet/components/SheetHeader.tsx +17 -2
  264. package/src/components/Skeleton/Skeleton.tsx +5 -3
  265. package/src/components/Sparkline/Sparkline.tsx +29 -36
  266. package/src/components/StatsCard/StatsCard.tsx +7 -6
  267. package/src/components/Tabs/components/TabsList.tsx +5 -3
  268. package/src/components/Tabs/components/TabsPanels.tsx +5 -3
  269. package/src/components/TagChip/TagChip.tsx +10 -3
  270. package/src/components/TagInput/TagInput.tsx +5 -3
  271. package/src/components/TimePicker/TimePicker.tsx +31 -13
  272. package/src/components/TimePicker/timepicker.types.ts +14 -13
  273. package/src/date-time/index.ts +61 -0
  274. package/src/date-time/iso.ts +511 -0
  275. package/src/date-time/now.ts +10 -9
  276. package/src/theme/index.ts +11 -3
  277. package/src/theme/schema.ts +82 -54
  278. package/src/theme/tokens.css +807 -0
  279. package/dist/styles.css +0 -2
  280. package/dist/theme.generated.css +0 -438
  281. package/src/styles/base.css +0 -41
  282. package/src/theme/palette.json +0 -100
  283. package/src/theme/palette.schema.json +0 -40
  284. package/src/theme/theme.json +0 -349
  285. package/src/theme/theme.schema.json +0 -876
  286. package/src/theme/typography.json +0 -7
  287. package/src/theme/typography.schema.json +0 -36
  288. package/tailwind.config.ts +0 -261
@@ -1,6 +1,6 @@
1
1
  import { Tooltip } from "@sixthshift/design-system/tooltip";
2
2
  import { cn } from "@sixthshift/design-system/utils";
3
- import type * as React from "react";
3
+ import * as React from "react";
4
4
 
5
5
  export type LineChartDataPoint = {
6
6
  label: string;
@@ -96,156 +96,162 @@ function buildPath(points: { x: number; y: number }[], interpolation: Interpolat
96
96
  return parts.join(" ");
97
97
  }
98
98
 
99
- export const LineChart = ({
100
- series,
101
- height = 200,
102
- showGrid = true,
103
- showAxes = true,
104
- showDots = true,
105
- showLabels = true,
106
- showValues = false,
107
- interpolation = "monotone",
108
- fillArea = false,
109
- yMin: yMinProp,
110
- yMax: yMaxProp,
111
- yTicks = 4,
112
- showTooltip = false,
113
- formatTooltip,
114
- formatValue = (v) => String(v),
115
- className,
116
- ...rest
117
- }: LineChartProps) => {
118
- const allValues = series.flatMap((s) => s.data.map((d) => d.value));
119
- // Math.min() of an empty list is Infinity, which propagates NaN through the
120
- // whole scale. Fall back to a plain 0-1 axis when there is no data.
121
- const rawMin = yMinProp ?? (allValues.length > 0 ? Math.min(...allValues) : 0);
122
- const rawMax = yMaxProp ?? (allValues.length > 0 ? Math.max(...allValues) : 1);
123
- const scale = niceScale(rawMin, rawMax, yTicks);
124
-
125
- const labelSet = new Set<string>();
126
- for (const s of series) {
127
- for (const d of s.data) {
128
- labelSet.add(d.label);
99
+ export const LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(
100
+ (
101
+ {
102
+ series,
103
+ height = 200,
104
+ showGrid = true,
105
+ showAxes = true,
106
+ showDots = true,
107
+ showLabels = true,
108
+ showValues = false,
109
+ interpolation = "monotone",
110
+ fillArea = false,
111
+ yMin: yMinProp,
112
+ yMax: yMaxProp,
113
+ yTicks = 4,
114
+ showTooltip = false,
115
+ formatTooltip,
116
+ formatValue = (v) => String(v),
117
+ className,
118
+ ...rest
119
+ }: LineChartProps,
120
+ ref
121
+ ) => {
122
+ const allValues = series.flatMap((s) => s.data.map((d) => d.value));
123
+ // Math.min() of an empty list is Infinity, which propagates NaN through the
124
+ // whole scale. Fall back to a plain 0-1 axis when there is no data.
125
+ const rawMin = yMinProp ?? (allValues.length > 0 ? Math.min(...allValues) : 0);
126
+ const rawMax = yMaxProp ?? (allValues.length > 0 ? Math.max(...allValues) : 1);
127
+ const scale = niceScale(rawMin, rawMax, yTicks);
128
+
129
+ const labelSet = new Set<string>();
130
+ for (const s of series) {
131
+ for (const d of s.data) {
132
+ labelSet.add(d.label);
133
+ }
129
134
  }
130
- }
131
- const labels = [...labelSet];
132
- const labelCount = labels.length;
133
- const labelIndex = new Map(labels.map((l, i) => [l, i]));
135
+ const labels = [...labelSet];
136
+ const labelCount = labels.length;
137
+ const labelIndex = new Map(labels.map((l, i) => [l, i]));
134
138
 
135
- const padding = showAxes ? PADDING : PADDING_NO_AXES;
136
- const viewWidth = 600;
137
- const chartW = viewWidth - padding.left - padding.right;
138
- const chartH = height - padding.top - padding.bottom;
139
+ const padding = showAxes ? PADDING : PADDING_NO_AXES;
140
+ const viewWidth = 600;
141
+ const chartW = viewWidth - padding.left - padding.right;
142
+ const chartH = height - padding.top - padding.bottom;
139
143
 
140
- const xStep = labelCount > 1 ? chartW / (labelCount - 1) : 0;
144
+ const xStep = labelCount > 1 ? chartW / (labelCount - 1) : 0;
141
145
 
142
- function toX(i: number) {
143
- return padding.left + i * xStep;
144
- }
146
+ function toX(i: number) {
147
+ return padding.left + i * xStep;
148
+ }
145
149
 
146
- function toY(v: number) {
147
- const ratio = (v - scale.min) / (scale.max - scale.min);
148
- return padding.top + chartH - ratio * chartH;
149
- }
150
+ function toY(v: number) {
151
+ const ratio = (v - scale.min) / (scale.max - scale.min);
152
+ return padding.top + chartH - ratio * chartH;
153
+ }
150
154
 
151
- const yTickValues: number[] = [];
152
- for (let v = scale.min; v <= scale.max + scale.step * 0.01; v += scale.step) {
153
- yTickValues.push(Math.round(v * 1e6) / 1e6);
154
- }
155
+ const yTickValues: number[] = [];
156
+ for (let v = scale.min; v <= scale.max + scale.step * 0.01; v += scale.step) {
157
+ yTickValues.push(Math.round(v * 1e6) / 1e6);
158
+ }
155
159
 
156
- function tooltipText(point: LineChartDataPoint, seriesName?: string) {
157
- if (formatTooltip) return formatTooltip(point, seriesName);
158
- const prefix = seriesName ? `${seriesName}: ` : `${point.label}: `;
159
- return `${prefix}${formatValue(point.value)}`;
160
- }
160
+ function tooltipText(point: LineChartDataPoint, seriesName?: string) {
161
+ if (formatTooltip) return formatTooltip(point, seriesName);
162
+ const prefix = seriesName ? `${seriesName}: ` : `${point.label}: `;
163
+ return `${prefix}${formatValue(point.value)}`;
164
+ }
165
+
166
+ return (
167
+ <div ref={ref} {...rest} className={cn("relative w-full", className)}>
168
+ <svg viewBox={`0 0 ${viewWidth} ${height}`} className="w-full" preserveAspectRatio="xMidYMid meet" role="img">
169
+ <title>Line chart</title>
170
+ {showGrid &&
171
+ yTickValues.map((v) => (
172
+ <line
173
+ key={`grid-${v}`}
174
+ x1={padding.left}
175
+ y1={toY(v)}
176
+ x2={viewWidth - padding.right}
177
+ y2={toY(v)}
178
+ className="stroke-border-subtle"
179
+ strokeWidth={1}
180
+ strokeDasharray="4 4"
181
+ />
182
+ ))}
161
183
 
162
- return (
163
- <div {...rest} className={cn("relative w-full", className)}>
164
- <svg viewBox={`0 0 ${viewWidth} ${height}`} className="w-full" preserveAspectRatio="xMidYMid meet" role="img">
165
- <title>Line chart</title>
166
- {showGrid &&
167
- yTickValues.map((v) => (
168
- <line
169
- key={`grid-${v}`}
170
- x1={padding.left}
171
- y1={toY(v)}
172
- x2={viewWidth - padding.right}
173
- y2={toY(v)}
174
- className="stroke-border-subtle"
175
- strokeWidth={1}
176
- strokeDasharray="4 4"
177
- />
178
- ))}
179
-
180
- {showAxes && (
181
- <>
182
- <line x1={padding.left} y1={padding.top} x2={padding.left} y2={padding.top + chartH} className="stroke-border-normal" strokeWidth={1} />
183
- <line
184
- x1={padding.left}
185
- y1={padding.top + chartH}
186
- x2={viewWidth - padding.right}
187
- y2={padding.top + chartH}
188
- className="stroke-border-normal"
189
- strokeWidth={1}
190
- />
191
- {yTickValues.map((v) => (
192
- <text key={`ytick-${v}`} x={padding.left - 8} y={toY(v)} textAnchor="end" dominantBaseline="middle" className="fill-fg-subtle" fontSize={11}>
193
- {formatValue(v)}
184
+ {showAxes && (
185
+ <>
186
+ <line x1={padding.left} y1={padding.top} x2={padding.left} y2={padding.top + chartH} className="stroke-border-normal" strokeWidth={1} />
187
+ <line
188
+ x1={padding.left}
189
+ y1={padding.top + chartH}
190
+ x2={viewWidth - padding.right}
191
+ y2={padding.top + chartH}
192
+ className="stroke-border-normal"
193
+ strokeWidth={1}
194
+ />
195
+ {yTickValues.map((v) => (
196
+ <text key={`ytick-${v}`} x={padding.left - 8} y={toY(v)} textAnchor="end" dominantBaseline="middle" className="fill-fg-subtle" fontSize={11}>
197
+ {formatValue(v)}
198
+ </text>
199
+ ))}
200
+ </>
201
+ )}
202
+
203
+ {showLabels &&
204
+ labels.map((label, i) => (
205
+ <text key={`xlabel-${label}`} x={toX(i)} y={height - 4} textAnchor="middle" className="fill-fg-subtle" fontSize={11}>
206
+ {label}
194
207
  </text>
195
208
  ))}
196
- </>
197
- )}
198
-
199
- {showLabels &&
200
- labels.map((label, i) => (
201
- <text key={`xlabel-${label}`} x={toX(i)} y={height - 4} textAnchor="middle" className="fill-fg-subtle" fontSize={11}>
202
- {label}
203
- </text>
204
- ))}
205
-
206
- {series.map((s, si) => {
207
- const color = s.color ?? SERIES_COLORS[si % SERIES_COLORS.length];
208
- const points = s.data.map((d) => ({ x: toX(labelIndex.get(d.label)!), y: toY(d.value) }));
209
- const path = buildPath(points, interpolation);
210
-
211
- return (
212
- <g key={s.name ?? si}>
213
- {fillArea && path && (
214
- <path
215
- d={`${path} L ${points[points.length - 1]!.x} ${padding.top + chartH} L ${points[0]!.x} ${padding.top + chartH} Z`}
216
- fill={color}
217
- opacity={0.1}
218
- />
219
- )}
220
- {path && <path d={path} fill="none" stroke={color} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />}
221
- {showDots && points.map((p, pi) => <circle key={`dot-${s.data[pi]!.label}`} cx={p.x} cy={p.y} r={3} fill={color} />)}
222
- {showValues &&
223
- points.map((p, vi) => (
224
- <text key={`val-${s.data[vi]!.label}`} x={p.x} y={p.y - 10} textAnchor="middle" className="fill-fg-normal" fontSize={10} fontWeight={600}>
225
- {formatValue(s.data[vi]!.value)}
226
- </text>
227
- ))}
228
- </g>
229
- );
230
- })}
231
- </svg>
232
-
233
- {showTooltip &&
234
- series.map((s, si) =>
235
- s.data.map((d) => {
236
- const xPct = (toX(labelIndex.get(d.label)!) / viewWidth) * 100;
237
- const yPct = (toY(d.value) / height) * 100;
209
+
210
+ {series.map((s, si) => {
211
+ const color = s.color ?? SERIES_COLORS[si % SERIES_COLORS.length];
212
+ const points = s.data.map((d) => ({ x: toX(labelIndex.get(d.label)!), y: toY(d.value) }));
213
+ const path = buildPath(points, interpolation);
238
214
 
239
215
  return (
240
- <Tooltip key={`tip-${s.name ?? si}-${d.label}`} delayShow={0}>
241
- <Tooltip.Trigger asChild>
242
- <span className="absolute block -translate-x-1/2 -translate-y-1/2" style={{ left: `${xPct}%`, top: `${yPct}%`, width: 16, height: 16 }} />
243
- </Tooltip.Trigger>
244
- <Tooltip.Body>{tooltipText(d, s.name)}</Tooltip.Body>
245
- </Tooltip>
216
+ <g key={s.name ?? si}>
217
+ {fillArea && path && (
218
+ <path
219
+ d={`${path} L ${points[points.length - 1]!.x} ${padding.top + chartH} L ${points[0]!.x} ${padding.top + chartH} Z`}
220
+ fill={color}
221
+ opacity={0.1}
222
+ />
223
+ )}
224
+ {path && <path d={path} fill="none" stroke={color} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />}
225
+ {showDots && points.map((p, pi) => <circle key={`dot-${s.data[pi]!.label}`} cx={p.x} cy={p.y} r={3} fill={color} />)}
226
+ {showValues &&
227
+ points.map((p, vi) => (
228
+ <text key={`val-${s.data[vi]!.label}`} x={p.x} y={p.y - 10} textAnchor="middle" className="fill-fg-normal" fontSize={10} fontWeight={600}>
229
+ {formatValue(s.data[vi]!.value)}
230
+ </text>
231
+ ))}
232
+ </g>
246
233
  );
247
- })
248
- )}
249
- </div>
250
- );
251
- };
234
+ })}
235
+ </svg>
236
+
237
+ {showTooltip &&
238
+ series.map((s, si) =>
239
+ s.data.map((d) => {
240
+ const xPct = (toX(labelIndex.get(d.label)!) / viewWidth) * 100;
241
+ const yPct = (toY(d.value) / height) * 100;
242
+
243
+ return (
244
+ <Tooltip key={`tip-${s.name ?? si}-${d.label}`} delayShow={0}>
245
+ <Tooltip.Trigger asChild>
246
+ <span className="absolute block -translate-x-1/2 -translate-y-1/2" style={{ left: `${xPct}%`, top: `${yPct}%`, width: 16, height: 16 }} />
247
+ </Tooltip.Trigger>
248
+ <Tooltip.Body>{tooltipText(d, s.name)}</Tooltip.Body>
249
+ </Tooltip>
250
+ );
251
+ })
252
+ )}
253
+ </div>
254
+ );
255
+ }
256
+ );
257
+ LineChart.displayName = "LineChart";
@@ -2,6 +2,7 @@ import { Body } from "@sixthshift/design-system/body";
2
2
  import { Emphasis } from "@sixthshift/design-system/emphasis";
3
3
  import { Heading } from "@sixthshift/design-system/heading";
4
4
  import { cn } from "@sixthshift/design-system/utils";
5
+ import * as React from "react";
5
6
  import ReactMarkdown from "react-markdown";
6
7
 
7
8
  export type MarkdownProps = {
@@ -9,8 +10,8 @@ export type MarkdownProps = {
9
10
  className?: string;
10
11
  };
11
12
 
12
- export const Markdown = ({ children, className }: MarkdownProps) => (
13
- <div className={cn("flex flex-col", className)}>
13
+ export const Markdown = React.forwardRef<HTMLDivElement, MarkdownProps>(({ children, className }, ref) => (
14
+ <div ref={ref} className={cn("flex flex-col", className)}>
14
15
  <ReactMarkdown
15
16
  components={{
16
17
  h1: ({ children }) => <Heading as="h3">{children}</Heading>,
@@ -36,5 +37,5 @@ export const Markdown = ({ children, className }: MarkdownProps) => (
36
37
  {children}
37
38
  </ReactMarkdown>
38
39
  </div>
39
- );
40
+ ));
40
41
  Markdown.displayName = "Markdown";
@@ -33,6 +33,22 @@ export type MessageProps = React.HTMLAttributes<HTMLDivElement> &
33
33
  icon?: React.ReactNode;
34
34
  };
35
35
 
36
+ /**
37
+ * A message is a live region, and the intent decides how rudely it interrupts.
38
+ * `danger` is the only intent that earns `role="alert"` — an assertive region
39
+ * cuts off whatever the screen reader was saying. Everything else is
40
+ * `role="status"` (polite), which is announced at the next natural pause. A
41
+ * success confirmation or a static "Note" callout stamped as `alert` trains
42
+ * users to ignore the role that actually matters. Consumers can override by
43
+ * passing their own `role` — `props` is spread after this.
44
+ */
45
+ const intentRole = {
46
+ neutral: "status",
47
+ success: "status",
48
+ warning: "status",
49
+ danger: "alert",
50
+ } as const;
51
+
36
52
  export const Message = React.forwardRef<HTMLDivElement, MessageProps>(({ className, intent = "neutral", size, title, icon, children, ...props }, ref) => {
37
53
  const hasCompoundChildren = React.Children.toArray(children).some(
38
54
  (child) =>
@@ -41,7 +57,7 @@ export const Message = React.forwardRef<HTMLDivElement, MessageProps>(({ classNa
41
57
  );
42
58
 
43
59
  return (
44
- <div ref={ref} role="alert" className={cn(messageVariants({ intent, size, className }))} {...props}>
60
+ <div ref={ref} role={intentRole[intent ?? "neutral"]} className={cn(messageVariants({ intent, size, className }))} {...props}>
45
61
  {hasCompoundChildren ? (
46
62
  children
47
63
  ) : (
@@ -1,13 +1,18 @@
1
1
  import { cn } from "@sixthshift/design-system/utils";
2
- import type { ReactNode } from "react";
2
+ import * as React from "react";
3
3
 
4
4
  export type MetricListProps = {
5
5
  /** Metric rows */
6
- children: ReactNode;
6
+ children: React.ReactNode;
7
7
  /** Additional CSS classes */
8
8
  className?: string;
9
9
  };
10
10
 
11
- export const MetricList = ({ children, className }: MetricListProps) => {
12
- return <div className={cn("flex flex-col gap-2 text-fg-subtle", className)}>{children}</div>;
13
- };
11
+ export const MetricList = React.forwardRef<HTMLDivElement, MetricListProps>(({ children, className }, ref) => {
12
+ return (
13
+ <div ref={ref} className={cn("flex flex-col gap-2 text-fg-subtle", className)}>
14
+ {children}
15
+ </div>
16
+ );
17
+ });
18
+ MetricList.displayName = "MetricList";
@@ -1,14 +1,14 @@
1
1
  import { FloatingFocusManager, FloatingOverlay, useDismiss, useFloating, useInteractions, useRole } from "@floating-ui/react";
2
2
  import { useMergedFloatingRef, usePresence } from "@sixthshift/design-system/hooks";
3
3
  import { cn } from "@sixthshift/design-system/utils";
4
- import { type HTMLAttributes, type ReactNode, useCallback, useContext, useEffect, useMemo } from "react";
4
+ import { forwardRef, type HTMLAttributes, type ReactNode, useCallback, useContext, useEffect, useId, useMemo, useState } from "react";
5
5
  import { ModalBody, ModalContext, ModalFooter, ModalHeader } from "./components";
6
6
 
7
7
  // =============================================================================
8
8
  // Types
9
9
  // =============================================================================
10
10
 
11
- export type ModalProps = Pick<HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
11
+ export type ModalProps = Pick<HTMLAttributes<HTMLDivElement>, "className" | "style" | "aria-label" | "aria-labelledby" | "aria-describedby"> & {
12
12
  /**
13
13
  * Called when the modal's open state should change. Today Modal is mount-driven
14
14
  * (parent conditionally renders), so this fires only with `false` on dismissal.
@@ -47,92 +47,124 @@ const sizeClasses = {
47
47
  // Modal
48
48
  // =============================================================================
49
49
 
50
- const ModalRoot = ({ onOpenChange, size = "md", className, style, children, dismissable = true, closable, align = "center" }: ModalProps) => {
51
- // Prefer explicit prop over context (context is set by useModal's overlay system)
52
- const contextValue = useContext(ModalContext);
53
- // Context provides a parameterless onClose for programmatic modals; adapt to the boolean shape.
54
- const handleDismiss = useCallback(() => {
55
- if (onOpenChange) onOpenChange(false);
56
- else contextValue?.onClose();
57
- }, [onOpenChange, contextValue]);
58
-
59
- const { ref: presenceRef, state, isMounted, show, hide } = usePresence();
60
-
61
- useEffect(() => {
62
- show();
63
- }, [show]);
64
-
65
- const handleClose = useCallback(() => {
66
- hide(handleDismiss);
67
- }, [handleDismiss, hide]);
68
-
69
- const { refs, context } = useFloating({
70
- open: true,
71
- onOpenChange: (nextOpen) => {
72
- if (!nextOpen) handleClose();
50
+ const ModalRoot = forwardRef<HTMLDivElement, ModalProps>(
51
+ (
52
+ {
53
+ onOpenChange,
54
+ size = "md",
55
+ className,
56
+ style,
57
+ children,
58
+ dismissable = true,
59
+ closable,
60
+ align = "center",
61
+ "aria-label": ariaLabel,
62
+ "aria-labelledby": ariaLabelledBy,
63
+ "aria-describedby": ariaDescribedBy,
73
64
  },
74
- });
75
-
76
- const mergedRef = useMergedFloatingRef(refs, presenceRef);
77
-
78
- const dismiss = useDismiss(context, {
79
- enabled: dismissable,
80
- // OverlayContext handles escape key for proper stack ordering
81
- escapeKey: false,
82
- outsidePress: true,
83
- outsidePressEvent: "mousedown",
84
- });
85
-
86
- const role = useRole(context, {
87
- role: "dialog",
88
- });
89
-
90
- const { getFloatingProps } = useInteractions([dismiss, role]);
91
-
92
- const modalContextValue = useMemo(() => ({ onClose: handleClose, closable }), [closable, handleClose]); // eslint-disable-line react-hooks/exhaustive-deps
93
-
94
- if (!isMounted) return null;
95
-
96
- const isEntering = state === "entering";
97
- const isExiting = state === "exiting";
98
-
99
- return (
100
- <FloatingFocusManager context={context} modal>
101
- <FloatingOverlay lockScroll className={cn("fixed inset-0 z-modal bg-bg-overlay", isEntering && "animate-fade-in", isExiting && "animate-fade-out")}>
102
- <div
103
- ref={mergedRef}
104
- role="dialog"
105
- aria-modal="true"
106
- tabIndex={-1}
107
- data-state={state}
108
- className={cn(
109
- "fixed flex flex-col overflow-hidden outline-hidden",
110
- "rounded-xl border border-border-normal bg-bg-normal",
111
- // Size
112
- sizeClasses[size],
113
- // Cap height so ModalBody scrolls instead of overflowing the viewport
114
- "max-h-[95%]",
115
- // Mobile: slide up from bottom, full width
116
- "max-sm:inset-x-0 max-sm:bottom-0 max-sm:rounded-b-none",
117
- isEntering && "max-sm:animate-slide-up-in",
118
- isExiting && "max-sm:animate-slide-up-out",
119
- // Desktop: horizontal center + vertical alignment
120
- "sm:left-1/2 sm:-translate-x-1/2",
121
- align === "center" && "sm:top-1/2 sm:-translate-y-1/2",
122
- align === "top" && "sm:top-[15vh]",
123
- isEntering && "sm:animate-fade-in",
124
- isExiting && "sm:animate-fade-out",
125
- className
126
- )}
127
- style={style}
128
- {...getFloatingProps({})}
129
- >
130
- <ModalContext.Provider value={modalContextValue}>{children}</ModalContext.Provider>
131
- </div>
132
- </FloatingOverlay>
133
- </FloatingFocusManager>
134
- );
135
- };
65
+ ref
66
+ ) => {
67
+ // Prefer explicit prop over context (context is set by useModal's overlay system)
68
+ const contextValue = useContext(ModalContext);
69
+ // Context provides a parameterless onClose for programmatic modals; adapt to the boolean shape.
70
+ const handleDismiss = useCallback(() => {
71
+ if (onOpenChange) onOpenChange(false);
72
+ else contextValue?.onClose();
73
+ }, [onOpenChange, contextValue]);
74
+
75
+ const { ref: presenceRef, state, isMounted, show, hide } = usePresence();
76
+
77
+ useEffect(() => {
78
+ show();
79
+ }, [show]);
80
+
81
+ const handleClose = useCallback(() => {
82
+ hide(handleDismiss);
83
+ }, [handleDismiss, hide]);
84
+
85
+ const { refs, context } = useFloating({
86
+ open: true,
87
+ onOpenChange: (nextOpen) => {
88
+ if (!nextOpen) handleClose();
89
+ },
90
+ });
91
+
92
+ const mergedRef = useMergedFloatingRef<HTMLDivElement>(refs, presenceRef, ref);
93
+
94
+ const dismiss = useDismiss(context, {
95
+ enabled: dismissable,
96
+ // OverlayContext handles escape key for proper stack ordering
97
+ escapeKey: false,
98
+ outsidePress: true,
99
+ outsidePressEvent: "mousedown",
100
+ });
101
+
102
+ const role = useRole(context, {
103
+ role: "dialog",
104
+ });
105
+
106
+ const { getFloatingProps } = useInteractions([dismiss, role]);
107
+
108
+ // The dialog names itself from its header. `Modal.Header` stamps `titleId`
109
+ // on itself and reports back, so a headerless modal falls through to the
110
+ // caller's `aria-label` instead of referencing an id that isn't there.
111
+ const titleId = useId();
112
+ const [hasTitle, setHasTitle] = useState(false);
113
+ const registerTitle = useCallback((present: boolean) => setHasTitle(present), []);
114
+
115
+ // `aria-labelledby` beats `aria-label` in the name computation, so a caller
116
+ // who passes a label must not also be pointed at the header — they would
117
+ // never see their own string win.
118
+ const labelledBy = ariaLabelledBy ?? (ariaLabel || !hasTitle ? undefined : titleId);
119
+
120
+ const modalContextValue = useMemo(() => ({ onClose: handleClose, closable, titleId, registerTitle }), [closable, handleClose, titleId, registerTitle]);
121
+
122
+ if (!isMounted) return null;
123
+
124
+ const isEntering = state === "entering";
125
+ const isExiting = state === "exiting";
126
+
127
+ return (
128
+ <FloatingFocusManager context={context} modal>
129
+ <FloatingOverlay lockScroll className={cn("fixed inset-0 z-modal bg-bg-overlay", isEntering && "animate-fade-in", isExiting && "animate-fade-out")}>
130
+ <div
131
+ ref={mergedRef}
132
+ role="dialog"
133
+ aria-modal="true"
134
+ aria-label={ariaLabel}
135
+ aria-labelledby={labelledBy}
136
+ aria-describedby={ariaDescribedBy}
137
+ tabIndex={-1}
138
+ data-state={state}
139
+ className={cn(
140
+ "fixed flex flex-col overflow-hidden outline-hidden",
141
+ "rounded-xl border border-border-normal bg-bg-normal",
142
+ // Size
143
+ sizeClasses[size],
144
+ // Cap height so ModalBody scrolls instead of overflowing the viewport
145
+ "max-h-[95%]",
146
+ // Mobile: slide up from bottom, full width
147
+ "max-sm:inset-x-0 max-sm:bottom-0 max-sm:rounded-b-none",
148
+ isEntering && "max-sm:animate-slide-up-in",
149
+ isExiting && "max-sm:animate-slide-up-out",
150
+ // Desktop: horizontal center + vertical alignment
151
+ "sm:left-1/2 sm:-translate-x-1/2",
152
+ align === "center" && "sm:top-1/2 sm:-translate-y-1/2",
153
+ align === "top" && "sm:top-[15vh]",
154
+ isEntering && "sm:animate-fade-in",
155
+ isExiting && "sm:animate-fade-out",
156
+ className
157
+ )}
158
+ style={style}
159
+ {...getFloatingProps({})}
160
+ >
161
+ <ModalContext.Provider value={modalContextValue}>{children}</ModalContext.Provider>
162
+ </div>
163
+ </FloatingOverlay>
164
+ </FloatingFocusManager>
165
+ );
166
+ }
167
+ );
136
168
  ModalRoot.displayName = "Modal";
137
169
 
138
170
  // =============================================================================
@@ -9,6 +9,20 @@ export type OnClose = () => void;
9
9
  interface ModalContextValue {
10
10
  onClose: OnClose;
11
11
  closable?: boolean | undefined;
12
+ /**
13
+ * The id `Modal.Header` stamps on itself so the dialog can name itself from
14
+ * its own title. A dialog with no accessible name is announced as just
15
+ * "dialog", which tells a screen-reader user nothing about what interrupted
16
+ * them.
17
+ */
18
+ titleId?: string;
19
+ /**
20
+ * The header reports its presence, because a headerless modal must not point
21
+ * `aria-labelledby` at an element that never rendered — a dangling reference
22
+ * is worse than no reference at all (it resolves to an empty name and axe
23
+ * flags it as an invalid attribute value).
24
+ */
25
+ registerTitle?: (present: boolean) => void;
12
26
  }
13
27
 
14
28
  export const ModalContext = createContext<ModalContextValue | null>(null);