@sixthshift/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 (236) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/dist/styles.css +2 -0
  4. package/dist/theme.generated.css +438 -0
  5. package/package.json +238 -0
  6. package/src/components/Avatar/Avatar.tsx +19 -0
  7. package/src/components/Avatar/index.ts +1 -0
  8. package/src/components/Badge/Badge.tsx +59 -0
  9. package/src/components/Badge/index.ts +1 -0
  10. package/src/components/BarChart/BarChart.tsx +51 -0
  11. package/src/components/BarChart/index.ts +1 -0
  12. package/src/components/Breadcrumb/Breadcrumb.tsx +47 -0
  13. package/src/components/Breadcrumb/index.ts +5 -0
  14. package/src/components/Button/Button.tsx +157 -0
  15. package/src/components/Button/index.ts +1 -0
  16. package/src/components/Calendar/Calendar.tsx +433 -0
  17. package/src/components/Calendar/calendar.hooks.ts +329 -0
  18. package/src/components/Calendar/calendar.types.ts +117 -0
  19. package/src/components/Calendar/index.ts +28 -0
  20. package/src/components/Card/Card.tsx +47 -0
  21. package/src/components/Card/index.ts +1 -0
  22. package/src/components/Checkbox/Checkbox.tsx +144 -0
  23. package/src/components/Checkbox/index.ts +1 -0
  24. package/src/components/CheckboxGroup/CheckboxGroup.tsx +173 -0
  25. package/src/components/CheckboxGroup/index.ts +5 -0
  26. package/src/components/Code/Editor/Editor.tsx +161 -0
  27. package/src/components/Code/Editor/index.ts +2 -0
  28. package/src/components/Code/Editor/semanticTokenProvider.ts +69 -0
  29. package/src/components/Code/Editor/themes.ts +112 -0
  30. package/src/components/Code/ValidationStatus/ValidationStatus.tsx +81 -0
  31. package/src/components/Code/ValidationStatus/index.ts +1 -0
  32. package/src/components/Code/Workspace/StatusBar.tsx +47 -0
  33. package/src/components/Code/Workspace/Toolbar.tsx +47 -0
  34. package/src/components/Code/Workspace/Workspace.tsx +109 -0
  35. package/src/components/Code/Workspace/index.ts +3 -0
  36. package/src/components/ColorDot/ColorDot.tsx +50 -0
  37. package/src/components/ColorDot/index.ts +1 -0
  38. package/src/components/DatePicker/DatePicker.tsx +236 -0
  39. package/src/components/DatePicker/datepicker.hooks.ts +381 -0
  40. package/src/components/DatePicker/datepicker.types.ts +140 -0
  41. package/src/components/DatePicker/index.ts +12 -0
  42. package/src/components/DateRangePicker/DateRangePicker.tsx +127 -0
  43. package/src/components/DateRangePicker/daterangepicker.types.ts +53 -0
  44. package/src/components/DateRangePicker/index.ts +2 -0
  45. package/src/components/DateTimePicker/DateTimePicker.tsx +399 -0
  46. package/src/components/DateTimePicker/datetimepicker.types.ts +48 -0
  47. package/src/components/DateTimePicker/index.ts +2 -0
  48. package/src/components/DateTimeRangePicker/DateTimeRangePicker.tsx +679 -0
  49. package/src/components/DateTimeRangePicker/datetimerangepicker.types.ts +62 -0
  50. package/src/components/DateTimeRangePicker/index.ts +2 -0
  51. package/src/components/EmptyState/EmptyState.tsx +29 -0
  52. package/src/components/EmptyState/index.ts +1 -0
  53. package/src/components/Field/Field.tsx +60 -0
  54. package/src/components/Field/index.ts +1 -0
  55. package/src/components/FormField/FormField.tsx +55 -0
  56. package/src/components/FormField/index.ts +2 -0
  57. package/src/components/HeatMap/HeatMapCalendar.tsx +127 -0
  58. package/src/components/HeatMap/HeatMapMatrix.tsx +75 -0
  59. package/src/components/HeatMap/heat-map.utils.ts +15 -0
  60. package/src/components/HeatMap/index.ts +3 -0
  61. package/src/components/HoverCard/HoverCard.tsx +83 -0
  62. package/src/components/HoverCard/components/HoverCardContent.tsx +30 -0
  63. package/src/components/HoverCard/components/HoverCardContext.tsx +22 -0
  64. package/src/components/HoverCard/components/HoverCardTrigger.tsx +32 -0
  65. package/src/components/HoverCard/index.ts +3 -0
  66. package/src/components/Input/Input.tsx +48 -0
  67. package/src/components/Input/index.ts +1 -0
  68. package/src/components/LineChart/LineChart.tsx +251 -0
  69. package/src/components/LineChart/index.ts +1 -0
  70. package/src/components/Markdown/Markdown.tsx +40 -0
  71. package/src/components/Markdown/index.ts +1 -0
  72. package/src/components/Message/Message.tsx +59 -0
  73. package/src/components/Message/components/MessageBody.tsx +9 -0
  74. package/src/components/Message/components/MessageDescription.tsx +9 -0
  75. package/src/components/Message/components/MessageIcon.tsx +13 -0
  76. package/src/components/Message/components/MessageTitle.tsx +9 -0
  77. package/src/components/Message/index.ts +10 -0
  78. package/src/components/MetricList/MetricList.tsx +13 -0
  79. package/src/components/MetricList/MetricRow.tsx +30 -0
  80. package/src/components/MetricList/index.ts +2 -0
  81. package/src/components/Modal/Modal.tsx +146 -0
  82. package/src/components/Modal/components/ModalBody.tsx +9 -0
  83. package/src/components/Modal/components/ModalContext.tsx +27 -0
  84. package/src/components/Modal/components/ModalFooter.tsx +9 -0
  85. package/src/components/Modal/components/ModalHeader.tsx +29 -0
  86. package/src/components/Modal/components/index.ts +9 -0
  87. package/src/components/Modal/index.ts +13 -0
  88. package/src/components/NavBottom/NavBottom.tsx +53 -0
  89. package/src/components/NavBottom/index.ts +3 -0
  90. package/src/components/NavSide/NavItem.tsx +39 -0
  91. package/src/components/NavSide/NavSide.tsx +40 -0
  92. package/src/components/NavSide/index.ts +2 -0
  93. package/src/components/NavSide/types.ts +36 -0
  94. package/src/components/Pagination/Pagination.tsx +86 -0
  95. package/src/components/Pagination/index.ts +2 -0
  96. package/src/components/Popover/Popover.tsx +68 -0
  97. package/src/components/Popover/components/PopoverBody.tsx +32 -0
  98. package/src/components/Popover/components/PopoverClose.tsx +26 -0
  99. package/src/components/Popover/components/PopoverContext.tsx +24 -0
  100. package/src/components/Popover/components/PopoverTrigger.tsx +31 -0
  101. package/src/components/Popover/index.ts +10 -0
  102. package/src/components/ProgressBar/ProgressBar.tsx +42 -0
  103. package/src/components/ProgressBar/index.ts +1 -0
  104. package/src/components/RadioButton/RadioButton.tsx +89 -0
  105. package/src/components/RadioButton/index.ts +1 -0
  106. package/src/components/RadioButtonGroup/RadioButtonGroup.tsx +193 -0
  107. package/src/components/RadioButtonGroup/index.ts +5 -0
  108. package/src/components/SearchInput/SearchInput.tsx +45 -0
  109. package/src/components/SearchInput/index.ts +2 -0
  110. package/src/components/Select/Select.tsx +289 -0
  111. package/src/components/Select/SelectDropdown.tsx +90 -0
  112. package/src/components/Select/SelectTrigger.tsx +130 -0
  113. package/src/components/Select/index.ts +3 -0
  114. package/src/components/Select/useSelectKeyboard.ts +111 -0
  115. package/src/components/Separator/Separator.tsx +20 -0
  116. package/src/components/Separator/index.ts +1 -0
  117. package/src/components/Sheet/Sheet.tsx +136 -0
  118. package/src/components/Sheet/components/SheetBody.tsx +9 -0
  119. package/src/components/Sheet/components/SheetContext.tsx +23 -0
  120. package/src/components/Sheet/components/SheetFooter.tsx +9 -0
  121. package/src/components/Sheet/components/SheetHeader.tsx +29 -0
  122. package/src/components/Sheet/components/index.ts +9 -0
  123. package/src/components/Sheet/index.ts +13 -0
  124. package/src/components/Skeleton/Skeleton.tsx +7 -0
  125. package/src/components/Skeleton/index.ts +1 -0
  126. package/src/components/Sparkline/Sparkline.tsx +93 -0
  127. package/src/components/Sparkline/index.ts +1 -0
  128. package/src/components/Spinner/Spinner.tsx +41 -0
  129. package/src/components/Spinner/index.ts +1 -0
  130. package/src/components/StatsCard/StatsCard.tsx +40 -0
  131. package/src/components/StatsCard/index.ts +1 -0
  132. package/src/components/Switch/Switch.tsx +128 -0
  133. package/src/components/Switch/index.ts +1 -0
  134. package/src/components/Tabs/Tabs.tsx +49 -0
  135. package/src/components/Tabs/components/TabsContext.tsx +35 -0
  136. package/src/components/Tabs/components/TabsList.tsx +98 -0
  137. package/src/components/Tabs/components/TabsPanels.tsx +26 -0
  138. package/src/components/Tabs/components/TabsTrigger.tsx +69 -0
  139. package/src/components/Tabs/index.ts +3 -0
  140. package/src/components/TagChip/TagChip.tsx +46 -0
  141. package/src/components/TagChip/index.ts +1 -0
  142. package/src/components/TagInput/TagInput.tsx +60 -0
  143. package/src/components/TagInput/index.ts +1 -0
  144. package/src/components/Text/Text.tsx +35 -0
  145. package/src/components/Text/index.ts +1 -0
  146. package/src/components/TextInline/TextInline.tsx +35 -0
  147. package/src/components/TextInline/index.ts +1 -0
  148. package/src/components/Textarea/Textarea.tsx +20 -0
  149. package/src/components/Textarea/index.ts +1 -0
  150. package/src/components/TimePicker/PeriodSelector.tsx +28 -0
  151. package/src/components/TimePicker/TimeColumn.tsx +62 -0
  152. package/src/components/TimePicker/TimePicker.tsx +323 -0
  153. package/src/components/TimePicker/index.ts +7 -0
  154. package/src/components/TimePicker/timepicker.hooks.ts +173 -0
  155. package/src/components/TimePicker/timepicker.types.ts +101 -0
  156. package/src/components/Toast/Toast.tsx +111 -0
  157. package/src/components/Toast/index.ts +1 -0
  158. package/src/components/Toggle/Toggle.tsx +72 -0
  159. package/src/components/Toggle/index.ts +1 -0
  160. package/src/components/ToggleGroup/ToggleGroup.tsx +14 -0
  161. package/src/components/ToggleGroup/ToggleGroupContainer.tsx +19 -0
  162. package/src/components/ToggleGroup/ToggleGroupItem.tsx +51 -0
  163. package/src/components/ToggleGroup/ToggleGroupMultiple.tsx +61 -0
  164. package/src/components/ToggleGroup/ToggleGroupSingle.tsx +105 -0
  165. package/src/components/ToggleGroup/index.ts +2 -0
  166. package/src/components/ToggleGroup/toggleGroup.types.ts +55 -0
  167. package/src/components/Tooltip/Tooltip.tsx +66 -0
  168. package/src/components/Tooltip/components/TooltipBody.tsx +30 -0
  169. package/src/components/Tooltip/components/TooltipContext.tsx +22 -0
  170. package/src/components/Tooltip/components/TooltipTrigger.tsx +27 -0
  171. package/src/components/Tooltip/index.ts +9 -0
  172. package/src/date-time/arithmetic.ts +101 -0
  173. package/src/date-time/boundaries.ts +91 -0
  174. package/src/date-time/compare.ts +85 -0
  175. package/src/date-time/duration.ts +63 -0
  176. package/src/date-time/elapsed.ts +18 -0
  177. package/src/date-time/format.ts +722 -0
  178. package/src/date-time/index.ts +279 -0
  179. package/src/date-time/now.ts +70 -0
  180. package/src/date-time/parse.ts +170 -0
  181. package/src/date-time/serialize.ts +90 -0
  182. package/src/date-time/timezone.ts +47 -0
  183. package/src/date-time/validate.ts +278 -0
  184. package/src/hooks/index.ts +10 -0
  185. package/src/hooks/useCollapsible.ts +16 -0
  186. package/src/hooks/useControllableState.ts +54 -0
  187. package/src/hooks/useDebouncedCallback.ts +21 -0
  188. package/src/hooks/useDebouncedState.ts +20 -0
  189. package/src/hooks/useDeviceFormFactor.ts +49 -0
  190. package/src/hooks/useLocalStorage.ts +67 -0
  191. package/src/hooks/useMergedFloatingRef.ts +29 -0
  192. package/src/hooks/usePresence.ts +127 -0
  193. package/src/hooks/useStack.ts +99 -0
  194. package/src/hooks/useTheme.ts +105 -0
  195. package/src/internal/Slot.tsx +79 -0
  196. package/src/internal/index.ts +2 -0
  197. package/src/internal/rovingFocus.ts +39 -0
  198. package/src/internal/types.ts +10 -0
  199. package/src/lib/EmptyBoundary.tsx +12 -0
  200. package/src/lib/ErrorBoundary.tsx +48 -0
  201. package/src/lib/contexts/ComponentsContext/ComponentsContext.tsx +34 -0
  202. package/src/lib/contexts/ComponentsContext/index.ts +1 -0
  203. package/src/lib/contexts/OverlayContext/OverlayContext.tsx +93 -0
  204. package/src/lib/contexts/OverlayContext/hooks/useModal.tsx +125 -0
  205. package/src/lib/contexts/OverlayContext/hooks/useToast.ts +80 -0
  206. package/src/lib/contexts/OverlayContext/index.ts +6 -0
  207. package/src/lib/format.ts +51 -0
  208. package/src/lib/utils.ts +10 -0
  209. package/src/lib/withEmpty.tsx +14 -0
  210. package/src/lib/withErrorBoundary.tsx +16 -0
  211. package/src/lib/withSuspense.tsx +16 -0
  212. package/src/lib/withSuspenseAndErrorBoundary.tsx +26 -0
  213. package/src/styles/base.css +41 -0
  214. package/src/theme/index.ts +37 -0
  215. package/src/theme/palette.json +100 -0
  216. package/src/theme/palette.schema.json +40 -0
  217. package/src/theme/schema.ts +122 -0
  218. package/src/theme/theme.json +349 -0
  219. package/src/theme/theme.schema.json +876 -0
  220. package/src/theme/typography.json +7 -0
  221. package/src/theme/typography.schema.json +36 -0
  222. package/src/typography/Body.tsx +8 -0
  223. package/src/typography/Caption.tsx +8 -0
  224. package/src/typography/Code.tsx +8 -0
  225. package/src/typography/Display.tsx +8 -0
  226. package/src/typography/Emphasis.tsx +8 -0
  227. package/src/typography/Heading.tsx +23 -0
  228. package/src/typography/Label.tsx +10 -0
  229. package/src/typography/Lead.tsx +8 -0
  230. package/src/typography/Mono.tsx +8 -0
  231. package/src/typography/Muted.tsx +8 -0
  232. package/src/typography/SectionTitle.tsx +8 -0
  233. package/src/typography/Subtitle.tsx +8 -0
  234. package/src/typography/Timestamp.tsx +8 -0
  235. package/src/typography/index.ts +10 -0
  236. package/tailwind.config.ts +261 -0
@@ -0,0 +1,62 @@
1
+ import type { Temporal, WeekStartsOn } from "../../date-time";
2
+
3
+ export type DisabledDateMatcher =
4
+ | Temporal.PlainDate
5
+ | Temporal.PlainDate[]
6
+ | ((date: Temporal.PlainDate) => boolean)
7
+ | { before: Temporal.PlainDate }
8
+ | { after: Temporal.PlainDate }
9
+ | { from: Temporal.PlainDate; to: Temporal.PlainDate };
10
+
11
+ export type DateTimeRangeValue = {
12
+ from: Temporal.Instant | undefined;
13
+ to: Temporal.Instant | undefined;
14
+ };
15
+
16
+ export type DateTimeRangePresetOption = {
17
+ label: string;
18
+ value: () => DateTimeRangeValue;
19
+ };
20
+
21
+ export type DateTimeRangePickerProps = {
22
+ // Value (controlled/uncontrolled) - NOW USES INSTANT
23
+ value?: DateTimeRangeValue | undefined;
24
+ defaultValue?: DateTimeRangeValue | undefined;
25
+ onChange?: (range: DateTimeRangeValue | undefined) => void;
26
+
27
+ // Presets
28
+ presets?: DateTimeRangePresetOption[];
29
+ showPresets?: boolean; // Default: true
30
+
31
+ // Date constraints
32
+ minDate?: Temporal.PlainDate;
33
+ maxDate?: Temporal.PlainDate;
34
+ disabledDates?: DisabledDateMatcher | DisabledDateMatcher[];
35
+
36
+ // Time constraints (apply to both start and end times)
37
+ minTime?: Temporal.PlainTime;
38
+ maxTime?: Temporal.PlainTime;
39
+ minuteStep?: 1 | 5 | 10 | 15 | 30;
40
+
41
+ // Time format
42
+ clockFormat?: "12h" | "24h"; // Default: "12h"
43
+ showSeconds?: boolean; // Default: false
44
+
45
+ // Display
46
+ placeholder?: string;
47
+ weekStartsOn?: WeekStartsOn;
48
+
49
+ // Form
50
+ name?: string;
51
+
52
+ // State
53
+ isDisabled?: boolean;
54
+ isInvalid?: boolean;
55
+
56
+ // Style
57
+ className?: string;
58
+ align?: "start" | "end";
59
+
60
+ /** Whether the clear button is shown when a value is present (default: true) */
61
+ clearable?: boolean;
62
+ };
@@ -0,0 +1,2 @@
1
+ export { DateTimeRangePicker } from "./DateTimeRangePicker";
2
+ export type { DateTimeRangePickerProps, DateTimeRangePresetOption, DateTimeRangeValue } from "./datetimerangepicker.types";
@@ -0,0 +1,29 @@
1
+ import { Muted } from "@sixthshift/design-system/muted";
2
+ import { cn } from "@sixthshift/design-system/utils";
3
+ import type { HTMLAttributes, ReactNode } from "react";
4
+
5
+ export type EmptyStateProps = HTMLAttributes<HTMLDivElement> & {
6
+ /** Optional icon to display */
7
+ icon?: ReactNode;
8
+ /** Message to display */
9
+ message: string;
10
+ /** Optional description for more context */
11
+ description?: string;
12
+ /** Optional action button or element */
13
+ action?: ReactNode;
14
+ /** Additional CSS classes */
15
+ className?: string;
16
+ };
17
+
18
+ export const EmptyState = ({ icon, message, description, action, className, ...props }: EmptyStateProps) => {
19
+ return (
20
+ <div className={cn("flex flex-col items-center justify-center gap-3 p-8 text-center", className)} {...props}>
21
+ {icon && <div className="text-fg-subtle">{icon}</div>}
22
+ <div className="flex flex-col gap-1">
23
+ <Muted>{message}</Muted>
24
+ {description && <span className="text-fg-subtle text-xs">{description}</span>}
25
+ </div>
26
+ {action && <div className="mt-2">{action}</div>}
27
+ </div>
28
+ );
29
+ };
@@ -0,0 +1 @@
1
+ export { EmptyState, type EmptyStateProps } from "./EmptyState";
@@ -0,0 +1,60 @@
1
+ import { cn } from "@sixthshift/design-system/utils";
2
+ import type { ReactNode } from "react";
3
+
4
+ /**
5
+ * The labelled identifier carve-out used by identity-disclosure surfaces
6
+ * (Person Detail's Identifiers card, Calendar Event's per-source provenance).
7
+ *
8
+ * Field exists because identifiers (JIDs, external IDs, OAuth subs) are
9
+ * *content* on these surfaces — they answer "what does PA know about this
10
+ * contact?" The default value rendering is monospaced with `break-all`
11
+ * because a raw JID is the canonical case and it must wrap inside the row.
12
+ *
13
+ * Sibling primitive — not a replacement: `MetricRow` (in `@sixthshift/design-system/metric-list`)
14
+ * carries runtime metrics ("CPU: 12.5%"). Both are label/value pairs but the
15
+ * value semantics differ: a metric is a short readable number; an identifier
16
+ * is a long opaque string. Forcing `MetricRow` to do both would conflate the
17
+ * two and break Identifiers-card wrapping.
18
+ *
19
+ * See: docs/engineering/explanation/concepts/identity-rendering.md
20
+ */
21
+ export type FieldProps = {
22
+ /** The humane label for what the identifier identifies ("WhatsApp", "Email"). */
23
+ label: string;
24
+ /** The identifier itself. Rendered monospaced + breakable by default. */
25
+ children: ReactNode;
26
+ /**
27
+ * `stacked` (default): label above, value below — best for long identifiers
28
+ * that need horizontal room to wrap.
29
+ * `row`: label and value on one line with the value right-aligned and
30
+ * wrapping — best for short identifiers (phone, short ID).
31
+ */
32
+ layout?: "stacked" | "row";
33
+ /**
34
+ * Set `false` to render the value in proportional text — e.g. when the
35
+ * value is itself a human-readable name (rare on identity-disclosure
36
+ * surfaces, but valid).
37
+ */
38
+ mono?: boolean;
39
+ className?: string;
40
+ };
41
+
42
+ export const Field = ({ label, children, layout = "stacked", mono = true, className }: FieldProps) => {
43
+ const valueClass = cn("text-fg-subtle text-xs", mono && "break-all font-mono");
44
+
45
+ if (layout === "row") {
46
+ return (
47
+ <div className={cn("flex items-baseline justify-between gap-3", className)}>
48
+ <span className="shrink-0 text-fg-subtle text-sm">{label}</span>
49
+ <span className={cn(valueClass, "min-w-0 text-right")}>{children}</span>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ return (
55
+ <div className={cn("flex flex-col gap-1", className)}>
56
+ <span className="text-fg-subtle text-xs uppercase tracking-wide">{label}</span>
57
+ <span className={valueClass}>{children}</span>
58
+ </div>
59
+ );
60
+ };
@@ -0,0 +1 @@
1
+ export { Field, type FieldProps } from "./Field";
@@ -0,0 +1,55 @@
1
+ import { Label } from "@sixthshift/design-system/label";
2
+ import { Message } from "@sixthshift/design-system/message";
3
+ import { Muted } from "@sixthshift/design-system/muted";
4
+ import { cn } from "@sixthshift/design-system/utils";
5
+ import * as React from "react";
6
+
7
+ export type FormFieldFeedback = {
8
+ message: string;
9
+ intent: "danger" | "success" | "warning";
10
+ };
11
+
12
+ export type FormFieldProps = React.HTMLAttributes<HTMLDivElement> & {
13
+ label: string;
14
+ description?: string;
15
+ feedback?: FormFieldFeedback;
16
+ required?: boolean;
17
+ };
18
+
19
+ const FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(({ className, label, description, feedback, required, children, id, ...props }, ref) => {
20
+ const generatedId = React.useId();
21
+ const fieldId = id ?? generatedId;
22
+ const descriptionId = description ? `${fieldId}-description` : undefined;
23
+ const feedbackId = feedback ? `${fieldId}-feedback` : undefined;
24
+
25
+ return (
26
+ <div ref={ref} className={cn("flex flex-col gap-1.5", className)} {...props}>
27
+ <Label htmlFor={fieldId}>
28
+ {label}
29
+ {required && <span className="ml-0.5 text-fg-danger">*</span>}
30
+ </Label>
31
+
32
+ {description && <Muted id={descriptionId}>{description}</Muted>}
33
+
34
+ {React.Children.map(children, (child) => {
35
+ if (React.isValidElement(child)) {
36
+ return React.cloneElement(child, {
37
+ id: fieldId,
38
+ "aria-describedby": cn(descriptionId, feedbackId) || undefined,
39
+ "aria-invalid": feedback?.intent === "danger" ? true : undefined,
40
+ } as React.HTMLAttributes<HTMLElement>);
41
+ }
42
+ return child;
43
+ })}
44
+
45
+ {feedback && (
46
+ <Message id={feedbackId} intent={feedback.intent} size="sm">
47
+ {feedback.message}
48
+ </Message>
49
+ )}
50
+ </div>
51
+ );
52
+ });
53
+ FormField.displayName = "FormField";
54
+
55
+ export { FormField };
@@ -0,0 +1,2 @@
1
+ export type { FormFieldFeedback, FormFieldProps } from "./FormField";
2
+ export { FormField } from "./FormField";
@@ -0,0 +1,127 @@
1
+ import { cn } from "@sixthshift/design-system/utils";
2
+ import type * as React from "react";
3
+ import { Temporal } from "../../date-time";
4
+ import { DAY_KEYS, DAY_LABELS, DEFAULT_COLOR_SCALE, getIntensity, type HeatMapCell, MONTH_NAMES } from "./heat-map.utils";
5
+
6
+ export type HeatMapCalendarProps = React.HTMLAttributes<HTMLDivElement> & {
7
+ data: HeatMapCell[];
8
+ colorScale?: string[];
9
+ cellSize?: number;
10
+ cellGap?: number;
11
+ formatTooltip?: (cell: HeatMapCell) => string;
12
+ };
13
+
14
+ type MonthGrid = {
15
+ year: number;
16
+ month: number;
17
+ label: string;
18
+ weeks: ({ date: string; value: number; col: number } | null)[][];
19
+ };
20
+
21
+ function buildMonthGrids(data: HeatMapCell[]): MonthGrid[] {
22
+ const dataMap = new Map(data.map((d) => [d.date, d.value]));
23
+
24
+ const dates = data.map((d) => Temporal.PlainDate.from(d.date)).sort(Temporal.PlainDate.compare);
25
+ const minDate = dates[0]!;
26
+ const maxDate = dates[dates.length - 1]!;
27
+
28
+ const months: MonthGrid[] = [];
29
+ let cursor = Temporal.PlainDate.from({ year: minDate.year, month: minDate.month, day: 1 });
30
+ const endMonth = Temporal.PlainDate.from({ year: maxDate.year, month: maxDate.month, day: 1 });
31
+
32
+ while (Temporal.PlainDate.compare(cursor, endMonth) <= 0) {
33
+ const year = cursor.year;
34
+ const month = cursor.month;
35
+ const daysInMonth = cursor.daysInMonth;
36
+
37
+ const weeks: ({ date: string; value: number; col: number } | null)[][] = [];
38
+ let currentWeek: ({ date: string; value: number; col: number } | null)[] = [];
39
+
40
+ const firstDow = cursor.dayOfWeek - 1;
41
+ for (let i = 0; i < firstDow; i++) {
42
+ currentWeek.push(null);
43
+ }
44
+
45
+ for (let day = 1; day <= daysInMonth; day++) {
46
+ const date = Temporal.PlainDate.from({ year, month, day });
47
+ const dateStr = date.toString();
48
+ const col = date.dayOfWeek - 1;
49
+
50
+ currentWeek.push({ date: dateStr, value: dataMap.get(dateStr) ?? 0, col });
51
+
52
+ if (col === 6 || day === daysInMonth) {
53
+ if (day === daysInMonth && col < 6) {
54
+ for (let i = col + 1; i <= 6; i++) {
55
+ currentWeek.push(null);
56
+ }
57
+ }
58
+ weeks.push(currentWeek);
59
+ currentWeek = [];
60
+ }
61
+ }
62
+
63
+ months.push({
64
+ year,
65
+ month,
66
+ label: `${MONTH_NAMES[month - 1]} ${year}`,
67
+ weeks,
68
+ });
69
+
70
+ cursor = month === 12 ? Temporal.PlainDate.from({ year: year + 1, month: 1, day: 1 }) : Temporal.PlainDate.from({ year, month: month + 1, day: 1 });
71
+ }
72
+
73
+ return months;
74
+ }
75
+
76
+ export const HeatMapCalendar = ({
77
+ data,
78
+ colorScale = DEFAULT_COLOR_SCALE,
79
+ cellSize = 12,
80
+ cellGap = 2,
81
+ formatTooltip,
82
+ className,
83
+ ...rest
84
+ }: HeatMapCalendarProps) => {
85
+ if (data.length === 0) return null;
86
+
87
+ const maxValue = Math.max(...data.map((d) => d.value), 1);
88
+ const months = buildMonthGrids(data);
89
+ const step = cellSize + cellGap;
90
+ const gridWidth = 7 * step;
91
+
92
+ return (
93
+ <div {...rest} className={cn("flex flex-wrap gap-4", className)}>
94
+ {months.map((month) => {
95
+ const headerHeight = 24;
96
+ const dayLabelHeight = 14;
97
+ const svgHeight = headerHeight + dayLabelHeight + month.weeks.length * step;
98
+
99
+ return (
100
+ <svg key={month.label} width={gridWidth} height={svgHeight} className="overflow-visible" role="img">
101
+ <title>{month.label}</title>
102
+ <text x={0} y={14} className="fill-fg-normal" fontSize={12} fontWeight={500}>
103
+ {month.label}
104
+ </text>
105
+ {DAY_LABELS.map((label, i) => (
106
+ <text key={`day-${DAY_KEYS[i]}`} x={i * step + cellSize / 2} y={headerHeight + 10} textAnchor="middle" className="fill-fg-subtle" fontSize={9}>
107
+ {label}
108
+ </text>
109
+ ))}
110
+ {month.weeks.map((week, wi) =>
111
+ week.map((cell) => {
112
+ if (!cell) return null;
113
+ const intensity = getIntensity(cell.value, maxValue, colorScale.length);
114
+ const fill = cell.value > 0 ? colorScale[intensity]! : colorScale[0]!;
115
+ return (
116
+ <rect key={cell.date} x={cell.col * step} y={headerHeight + dayLabelHeight + wi * step} width={cellSize} height={cellSize} rx={2} fill={fill}>
117
+ {formatTooltip && <title>{formatTooltip({ date: cell.date, value: cell.value })}</title>}
118
+ </rect>
119
+ );
120
+ })
121
+ )}
122
+ </svg>
123
+ );
124
+ })}
125
+ </div>
126
+ );
127
+ };
@@ -0,0 +1,75 @@
1
+ import { cn } from "@sixthshift/design-system/utils";
2
+ import type * as React from "react";
3
+ import { Temporal } from "../../date-time";
4
+ import { DAY_KEYS, DAY_LABELS, DEFAULT_COLOR_SCALE, getIntensity, type HeatMapCell } from "./heat-map.utils";
5
+
6
+ export type HeatMapMatrixProps = React.HTMLAttributes<HTMLDivElement> & {
7
+ data: HeatMapCell[];
8
+ colorScale?: string[];
9
+ cellSize?: number;
10
+ cellGap?: number;
11
+ showDayLabels?: boolean;
12
+ formatTooltip?: (cell: HeatMapCell) => string;
13
+ };
14
+
15
+ export const HeatMapMatrix = ({
16
+ data,
17
+ colorScale = DEFAULT_COLOR_SCALE,
18
+ cellSize = 12,
19
+ cellGap = 2,
20
+ showDayLabels = true,
21
+ formatTooltip,
22
+ className,
23
+ ...rest
24
+ }: HeatMapMatrixProps) => {
25
+ if (data.length === 0) return null;
26
+
27
+ const maxValue = Math.max(...data.map((d) => d.value), 1);
28
+
29
+ const sorted = [...data].map((d) => ({ ...d, plain: Temporal.PlainDate.from(d.date) })).sort((a, b) => Temporal.PlainDate.compare(a.plain, b.plain));
30
+
31
+ const rows: { date: string; value: number; col: number }[][] = [];
32
+ let currentRow: { date: string; value: number; col: number }[] = [];
33
+ let lastDow = -1;
34
+
35
+ for (const cell of sorted) {
36
+ const dow = cell.plain.dayOfWeek - 1;
37
+ if (dow <= lastDow && currentRow.length > 0) {
38
+ rows.push(currentRow);
39
+ currentRow = [];
40
+ }
41
+ currentRow.push({ date: cell.date, value: cell.value, col: dow });
42
+ lastDow = dow;
43
+ }
44
+ if (currentRow.length > 0) rows.push(currentRow);
45
+
46
+ const step = cellSize + cellGap;
47
+ const labelHeight = showDayLabels ? 14 : 0;
48
+ const gridWidth = 7 * step;
49
+ const gridHeight = rows.length * step;
50
+
51
+ return (
52
+ <div {...rest} className={cn("inline-block", className)}>
53
+ <svg width={gridWidth} height={gridHeight + labelHeight} className="overflow-visible" role="img">
54
+ <title>Heat map</title>
55
+ {showDayLabels &&
56
+ DAY_LABELS.map((label, i) => (
57
+ <text key={`col-${DAY_KEYS[i]}`} x={i * step + cellSize / 2} y={10} textAnchor="middle" className="fill-fg-subtle" fontSize={9}>
58
+ {label}
59
+ </text>
60
+ ))}
61
+ {rows.map((row, ri) =>
62
+ row.map((cell) => {
63
+ const intensity = getIntensity(cell.value, maxValue, colorScale.length);
64
+ const fill = cell.value > 0 ? colorScale[intensity]! : colorScale[0]!;
65
+ return (
66
+ <rect key={cell.date} x={cell.col * step} y={labelHeight + ri * step} width={cellSize} height={cellSize} rx={2} fill={fill}>
67
+ {formatTooltip && <title>{formatTooltip({ date: cell.date, value: cell.value })}</title>}
68
+ </rect>
69
+ );
70
+ })
71
+ )}
72
+ </svg>
73
+ </div>
74
+ );
75
+ };
@@ -0,0 +1,15 @@
1
+ export type HeatMapCell = {
2
+ date: string;
3
+ value: number;
4
+ };
5
+
6
+ export const DAY_LABELS = ["M", "T", "W", "T", "F", "S", "S"];
7
+ export const DAY_KEYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
8
+ export const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
9
+
10
+ export const DEFAULT_COLOR_SCALE = ["var(--bg-subtle)", "var(--bg-brand-subtle)", "var(--bg-brand)", "var(--bg-brand-strong)"];
11
+
12
+ export function getIntensity(value: number, max: number, levels: number): number {
13
+ if (value <= 0 || max <= 0) return 0;
14
+ return Math.ceil(Math.min(value / max, 1) * (levels - 1));
15
+ }
@@ -0,0 +1,3 @@
1
+ export { HeatMapCalendar, type HeatMapCalendarProps } from "./HeatMapCalendar";
2
+ export { HeatMapMatrix, type HeatMapMatrixProps } from "./HeatMapMatrix";
3
+ export type { HeatMapCell } from "./heat-map.utils";
@@ -0,0 +1,83 @@
1
+ import { autoUpdate, flip, offset, type Placement, safePolygon, shift, useDismiss, useFloating, useFocus, useHover, useInteractions } from "@floating-ui/react";
2
+ import { useControllableState } from "@sixthshift/design-system/hooks";
3
+ import type * as React from "react";
4
+ import { HoverCardContent } from "./components/HoverCardContent";
5
+ import { HoverCardContext } from "./components/HoverCardContext";
6
+ import { HoverCardTrigger } from "./components/HoverCardTrigger";
7
+
8
+ export type HoverCardProps = {
9
+ /** Controlled open state */
10
+ open?: boolean;
11
+ /** Callback when open state changes */
12
+ onOpenChange?: (open: boolean) => void;
13
+ /** Default open state for uncontrolled mode */
14
+ defaultOpen?: boolean;
15
+ /** Placement relative to trigger */
16
+ placement?: Placement;
17
+ /** Delay before showing (ms). Defaults to 500ms — longer than a tooltip so hover-cards don't pop on incidental hover. */
18
+ delayShow?: number;
19
+ /** Delay before hiding (ms). Defaults to 200ms. */
20
+ delayHide?: number;
21
+ /** Offset from trigger in pixels */
22
+ offsetPx?: number;
23
+ /** Children — compound components */
24
+ children: React.ReactNode;
25
+ };
26
+
27
+ const HoverCardRoot = ({
28
+ open: controlledOpen,
29
+ onOpenChange,
30
+ defaultOpen = false,
31
+ placement = "bottom-start",
32
+ delayShow = 500,
33
+ delayHide = 200,
34
+ offsetPx = 8,
35
+ children,
36
+ }: HoverCardProps) => {
37
+ const [open, setOpen] = useControllableState({
38
+ value: controlledOpen,
39
+ defaultValue: defaultOpen,
40
+ onChange: onOpenChange,
41
+ });
42
+
43
+ const { refs, floatingStyles, context } = useFloating({
44
+ open,
45
+ onOpenChange: setOpen,
46
+ placement,
47
+ strategy: "fixed",
48
+ middleware: [offset(offsetPx), flip(), shift({ padding: 8 })],
49
+ whileElementsMounted: autoUpdate,
50
+ });
51
+
52
+ const hover = useHover(context, {
53
+ delay: { open: delayShow, close: delayHide },
54
+ handleClose: safePolygon(),
55
+ });
56
+ const focus = useFocus(context);
57
+ const dismiss = useDismiss(context);
58
+
59
+ const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus, dismiss]);
60
+
61
+ const contextValue = {
62
+ open,
63
+ refs: {
64
+ setReference: refs.setReference,
65
+ setFloating: refs.setFloating,
66
+ },
67
+ floatingStyles,
68
+ getReferenceProps,
69
+ getFloatingProps,
70
+ };
71
+
72
+ return <HoverCardContext.Provider value={contextValue}>{children}</HoverCardContext.Provider>;
73
+ };
74
+
75
+ type HoverCardComponent = typeof HoverCardRoot & {
76
+ Trigger: typeof HoverCardTrigger;
77
+ Content: typeof HoverCardContent;
78
+ };
79
+
80
+ export const HoverCard: HoverCardComponent = Object.assign(HoverCardRoot, {
81
+ Trigger: HoverCardTrigger,
82
+ Content: HoverCardContent,
83
+ });
@@ -0,0 +1,30 @@
1
+ import { FloatingPortal } from "@floating-ui/react";
2
+ import { cn } from "@sixthshift/design-system/utils";
3
+ import * as React from "react";
4
+ import { useHoverCardContext } from "./HoverCardContext";
5
+
6
+ export type HoverCardContentProps = React.HTMLAttributes<HTMLDivElement>;
7
+
8
+ export const HoverCardContent = React.forwardRef<HTMLDivElement, HoverCardContentProps>(({ className, children, ...props }, forwardedRef) => {
9
+ const { open, refs, floatingStyles, getFloatingProps } = useHoverCardContext();
10
+
11
+ if (!open) return null;
12
+
13
+ return (
14
+ <FloatingPortal>
15
+ <div
16
+ ref={(node) => {
17
+ refs.setFloating(node);
18
+ if (typeof forwardedRef === "function") forwardedRef(node);
19
+ else if (forwardedRef) forwardedRef.current = node;
20
+ }}
21
+ style={floatingStyles}
22
+ className={cn("z-popover w-80 rounded-lg border border-border-normal bg-bg-normal p-4 shadow-lg", className)}
23
+ {...getFloatingProps(props)}
24
+ >
25
+ {children}
26
+ </div>
27
+ </FloatingPortal>
28
+ );
29
+ });
30
+ HoverCardContent.displayName = "HoverCardContent";
@@ -0,0 +1,22 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ export type HoverCardContextValue = {
4
+ open: boolean;
5
+ refs: {
6
+ setReference: (node: HTMLElement | null) => void;
7
+ setFloating: (node: HTMLElement | null) => void;
8
+ };
9
+ floatingStyles: React.CSSProperties;
10
+ getReferenceProps: (props?: React.HTMLProps<HTMLElement>) => Record<string, unknown>;
11
+ getFloatingProps: (props?: React.HTMLProps<HTMLElement>) => Record<string, unknown>;
12
+ };
13
+
14
+ export const HoverCardContext = createContext<HoverCardContextValue | null>(null);
15
+
16
+ export function useHoverCardContext() {
17
+ const context = useContext(HoverCardContext);
18
+ if (!context) {
19
+ throw new Error("HoverCard compound components must be used within <HoverCard>");
20
+ }
21
+ return context;
22
+ }
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+ import { Slot } from "../../../internal/Slot";
3
+ import type { WritableRefObject } from "../../../internal/types";
4
+ import { useHoverCardContext } from "./HoverCardContext";
5
+
6
+ export type HoverCardTriggerProps = React.HTMLAttributes<HTMLElement> & {
7
+ /** Render as child element (default true — hover-cards typically wrap an existing inline element). */
8
+ asChild?: boolean;
9
+ };
10
+
11
+ export const HoverCardTrigger = React.forwardRef<HTMLElement, HoverCardTriggerProps>(({ asChild = true, children, className, ...props }, forwardedRef) => {
12
+ const { refs, getReferenceProps } = useHoverCardContext();
13
+ const Comp = asChild ? Slot : "span";
14
+
15
+ return (
16
+ <Comp
17
+ ref={(node: HTMLElement | null) => {
18
+ refs.setReference(node);
19
+ if (typeof forwardedRef === "function") forwardedRef(node);
20
+ else if (forwardedRef) (forwardedRef as WritableRefObject<HTMLElement | null>).current = node;
21
+ }}
22
+ // When rendering as a span wrapper (not asChild), use block layout so the
23
+ // bounding box matches the wrapped element. Without this, the span has
24
+ // inline layout and floating-ui anchors to the wrong position.
25
+ className={asChild ? className : `block ${className ?? ""}`.trim()}
26
+ {...getReferenceProps(props)}
27
+ >
28
+ {children}
29
+ </Comp>
30
+ );
31
+ });
32
+ HoverCardTrigger.displayName = "HoverCardTrigger";
@@ -0,0 +1,3 @@
1
+ export { HoverCardContent, type HoverCardContentProps } from "./components/HoverCardContent";
2
+ export { HoverCardTrigger, type HoverCardTriggerProps } from "./components/HoverCardTrigger";
3
+ export { HoverCard, type HoverCardProps } from "./HoverCard";
@@ -0,0 +1,48 @@
1
+ import { cn } from "@sixthshift/design-system/utils";
2
+ import * as React from "react";
3
+
4
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
5
+ iconLeft?: React.ReactNode;
6
+ iconRight?: React.ReactNode;
7
+ };
8
+
9
+ const iconSlotStyles = `absolute top-1/2 -translate-y-1/2 text-fg-subtle
10
+ [&_svg]:h-4 [&_svg]:w-4
11
+ [&_button]:flex [&_button]:cursor-pointer [&_button]:appearance-none
12
+ [&_button]:border-0 [&_button]:bg-transparent`;
13
+
14
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, iconLeft, iconRight, ...props }, ref) => {
15
+ if (iconLeft || iconRight) {
16
+ return (
17
+ <div className={cn("relative h-9 w-full text-sm", className)}>
18
+ {iconLeft && <span className={cn(iconSlotStyles, "left-3")}>{iconLeft}</span>}
19
+ <input
20
+ type={type}
21
+ className={cn(
22
+ `flex h-full w-full rounded-md border border-border-normal bg-bg-normal py-1 text-[length:inherit] shadow-xs transition-colors file:border-0 file:bg-bg-normal file:font-medium file:text-fg-normal file:text-sm placeholder:text-fg-subtle focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50`,
23
+ iconLeft ? "pl-9" : "pl-3",
24
+ iconRight ? "pr-9" : "pr-3"
25
+ )}
26
+ ref={ref}
27
+ {...props}
28
+ />
29
+ {iconRight && <span className={cn(iconSlotStyles, "right-3")}>{iconRight}</span>}
30
+ </div>
31
+ );
32
+ }
33
+
34
+ return (
35
+ <input
36
+ type={type}
37
+ className={cn(
38
+ `flex h-9 w-full rounded-md border border-border-normal bg-bg-normal px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-bg-normal file:font-medium file:text-fg-normal file:text-sm placeholder:text-fg-subtle focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50`,
39
+ className
40
+ )}
41
+ ref={ref}
42
+ {...props}
43
+ />
44
+ );
45
+ });
46
+ Input.displayName = "Input";
47
+
48
+ export { Input };
@@ -0,0 +1 @@
1
+ export { Input, type InputProps } from "./Input";