sid-ui 0.0.0-stage → 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 (116) hide show
  1. package/README.md +54 -2
  2. package/package.json +51 -4
  3. package/src/brand/BrandFooter.tsx +45 -0
  4. package/src/brand/BrandHeading.tsx +24 -0
  5. package/src/brand/BrandLockup.tsx +47 -0
  6. package/src/brand/JoinedLogo.tsx +67 -0
  7. package/src/brand/JoinedMark.tsx +76 -0
  8. package/src/brand/geometry.ts +26 -0
  9. package/src/brand/index.ts +11 -0
  10. package/src/brand/name.ts +5 -0
  11. package/src/brand/paint.tsx +56 -0
  12. package/src/components/Action.tsx +44 -0
  13. package/src/components/AddressSelector.tsx +99 -0
  14. package/src/components/BarChart.tsx +132 -0
  15. package/src/components/Calendar.tsx +284 -0
  16. package/src/components/CalendarAgenda.tsx +87 -0
  17. package/src/components/CalendarMonth.tsx +223 -0
  18. package/src/components/CalendarSchedule.tsx +210 -0
  19. package/src/components/Card.tsx +80 -0
  20. package/src/components/Charts.tsx +64 -0
  21. package/src/components/Chat.tsx +35 -0
  22. package/src/components/CitySelector.tsx +81 -0
  23. package/src/components/Clock.tsx +196 -0
  24. package/src/components/CodeInput.tsx +183 -0
  25. package/src/components/Container.tsx +20 -0
  26. package/src/components/Content.tsx +50 -0
  27. package/src/components/Data.tsx +43 -0
  28. package/src/components/DataInput.tsx +107 -0
  29. package/src/components/DateField.tsx +162 -0
  30. package/src/components/DonutChart.tsx +123 -0
  31. package/src/components/Drawer.tsx +110 -0
  32. package/src/components/Feedback.tsx +37 -0
  33. package/src/components/FileUploader.tsx +377 -0
  34. package/src/components/FunnelChart.tsx +83 -0
  35. package/src/components/Glyph.tsx +330 -0
  36. package/src/components/GoogleSignIn.tsx +67 -0
  37. package/src/components/GridSystem.tsx +131 -0
  38. package/src/components/HeatmapCalendar.tsx +167 -0
  39. package/src/components/KanbanBoard.tsx +421 -0
  40. package/src/components/Kpi.tsx +61 -0
  41. package/src/components/LayoutPrimitives.tsx +86 -0
  42. package/src/components/Lists.tsx +19 -0
  43. package/src/components/LocationSelector.tsx +49 -0
  44. package/src/components/MessageList.tsx +80 -0
  45. package/src/components/Nav.tsx +69 -0
  46. package/src/components/Navigation.tsx +52 -0
  47. package/src/components/Notification.tsx +115 -0
  48. package/src/components/NotificationTrigger.tsx +248 -0
  49. package/src/components/Overlay.tsx +50 -0
  50. package/src/components/Page.tsx +143 -0
  51. package/src/components/PageTabs.tsx +28 -0
  52. package/src/components/PickerShell.tsx +138 -0
  53. package/src/components/PillNav.tsx +194 -0
  54. package/src/components/Primitives.tsx +30 -0
  55. package/src/components/Rating.tsx +78 -0
  56. package/src/components/Responsive.tsx +128 -0
  57. package/src/components/ResponsiveFrame.tsx +122 -0
  58. package/src/components/Segment.tsx +190 -0
  59. package/src/components/Sparkline.tsx +52 -0
  60. package/src/components/StateSelector.tsx +39 -0
  61. package/src/components/Sticky.tsx +95 -0
  62. package/src/components/Table.tsx +344 -0
  63. package/src/components/ThemeToggle.tsx +48 -0
  64. package/src/components/Tile.tsx +23 -0
  65. package/src/components/TimeColumns.tsx +155 -0
  66. package/src/components/TimeDial.tsx +260 -0
  67. package/src/components/TimeField.tsx +196 -0
  68. package/src/components/TimeInput.tsx +213 -0
  69. package/src/components/TimeSlots.tsx +83 -0
  70. package/src/components/Timeline.tsx +111 -0
  71. package/src/components/TokenDemo.tsx +378 -0
  72. package/src/components/Tree.tsx +283 -0
  73. package/src/components/TrendChart.tsx +211 -0
  74. package/src/components/breakpoints.ts +42 -0
  75. package/src/components/calendarTypes.ts +26 -0
  76. package/src/components/chartMath.ts +125 -0
  77. package/src/components/chartParts.tsx +116 -0
  78. package/src/components/codeInputRules.ts +58 -0
  79. package/src/components/date.ts +87 -0
  80. package/src/components/hooks.ts +53 -0
  81. package/src/components/index.ts +195 -0
  82. package/src/components/kanban.ts +78 -0
  83. package/src/components/motion.ts +78 -0
  84. package/src/components/places-search.tsx +73 -0
  85. package/src/components/places.ts +209 -0
  86. package/src/components/size.ts +3 -0
  87. package/src/components/time.ts +83 -0
  88. package/src/components/useFileDrop.ts +104 -0
  89. package/src/places/geoapify.ts +129 -0
  90. package/src/styles/astryx.css +5 -0
  91. package/src/styles/components/brand.css +98 -0
  92. package/src/styles/components/calendar.css +680 -0
  93. package/src/styles/components/charts.css +659 -0
  94. package/src/styles/components/code-input.css +233 -0
  95. package/src/styles/components/google.css +11 -0
  96. package/src/styles/components/message-list.css +153 -0
  97. package/src/styles/components/notification.css +350 -0
  98. package/src/styles/components/picker.css +114 -0
  99. package/src/styles/components/pill-nav.css +179 -0
  100. package/src/styles/components/responsive.css +419 -0
  101. package/src/styles/components/sticky.css +23 -0
  102. package/src/styles/components/table.css +267 -0
  103. package/src/styles/components/time.css +526 -0
  104. package/src/styles/components/timeline.css +426 -0
  105. package/src/styles/components/tree.css +269 -0
  106. package/src/styles/components.css +423 -0
  107. package/src/styles/joined.css +4 -0
  108. package/src/styles/theme.css +79 -0
  109. package/src/styles/tokens.css +338 -0
  110. package/src/theme/JoinedProvider.tsx +50 -0
  111. package/src/theme/index.ts +3 -0
  112. package/src/theme/joined.d.ts +11 -0
  113. package/src/theme/joined.js +569 -0
  114. package/src/theme/joined.theme.ts +74 -0
  115. package/src/theme/joined.variants.d.ts +16 -0
  116. package/src/theme/theme.css +786 -0
@@ -0,0 +1,330 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ /**
4
+ * Stroke icons on a 24×24 grid: round caps and joins, 2px stroke.
5
+ * Geometry is the Lucide set (ISC) so every glyph shares one weight,
6
+ * one optical box, and one corner treatment.
7
+ */
8
+ type GlyphMark =
9
+ | { d: string }
10
+ | { cx: string; cy: string; r: string; fill?: "currentColor"; stroke?: "none" }
11
+ | { x1: string; y1: string; x2: string; y2: string }
12
+ | { x: string; y: string; width: string; height: string; rx?: string; ry?: string };
13
+
14
+ const MARKS = {
15
+ chevronRight: [{ d: "m9 18 6-6-6-6" }],
16
+ chevronLeft: [{ d: "m15 18-6-6 6-6" }],
17
+ chevronDown: [{ d: "m6 9 6 6 6-6" }],
18
+ chevronUp: [{ d: "m18 15-6-6-6 6" }],
19
+ check: [{ d: "M20 6 9 17l-5-5" }],
20
+ minus: [{ d: "M5 12h14" }],
21
+ plus: [{ d: "M5 12h14" }, { d: "M12 5v14" }],
22
+ close: [{ d: "M18 6 6 18" }, { d: "m6 6 12 12" }],
23
+ clock: [{ cx: "12", cy: "12", r: "10" }, { d: "M12 6v6l4 2" }],
24
+ calendar: [
25
+ { d: "M8 2v3" },
26
+ { d: "M16 2v3" },
27
+ { x: "3", y: "3", width: "18", height: "18", rx: "2" },
28
+ { d: "M3 9h18" },
29
+ ],
30
+ arrowUp: [{ d: "m5 12 7-7 7 7" }, { d: "M12 19V5" }],
31
+ arrowDown: [{ d: "M12 5v14" }, { d: "m19 12-7 7-7-7" }],
32
+ arrowRight: [{ d: "M5 12h14" }, { d: "m12 5 7 7-7 7" }],
33
+ arrowLeft: [{ d: "m12 19-7-7 7-7" }, { d: "M19 12H5" }],
34
+ sort: [{ d: "m7 15 5 5 5-5" }, { d: "m7 9 5-5 5 5" }],
35
+ folder: [
36
+ {
37
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",
38
+ },
39
+ ],
40
+ folderOpen: [
41
+ {
42
+ d: "m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",
43
+ },
44
+ ],
45
+ file: [
46
+ {
47
+ d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
48
+ },
49
+ { d: "M14 2v5a1 1 0 0 0 1 1h5" },
50
+ ],
51
+ search: [{ d: "m21 21-4.34-4.34" }, { cx: "11", cy: "11", r: "8" }],
52
+ dot: [{ cx: "12", cy: "12", r: "1.25", fill: "currentColor", stroke: "none" }],
53
+ edit: [
54
+ {
55
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
56
+ },
57
+ { d: "m15 5 4 4" },
58
+ ],
59
+ trash: [
60
+ { d: "M10 11v6" },
61
+ { d: "M14 11v6" },
62
+ { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" },
63
+ { d: "M3 6h18" },
64
+ { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" },
65
+ ],
66
+ share: [
67
+ { d: "M12 2v13" },
68
+ { d: "m16 6-4-4-4 4" },
69
+ { d: "M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" },
70
+ ],
71
+ download: [
72
+ { d: "M12 15V3" },
73
+ { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" },
74
+ { d: "m7 10 5 5 5-5" },
75
+ ],
76
+ upload: [
77
+ { d: "M12 3v12" },
78
+ { d: "m17 8-5-5-5 5" },
79
+ { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" },
80
+ ],
81
+ heart: [
82
+ {
83
+ d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",
84
+ },
85
+ ],
86
+ star: [
87
+ {
88
+ d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",
89
+ },
90
+ ],
91
+ bookmark: [
92
+ {
93
+ d: "M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z",
94
+ },
95
+ ],
96
+ bold: [{ d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8" }],
97
+ italic: [
98
+ { x1: "19", y1: "4", x2: "10", y2: "4" },
99
+ { x1: "14", y1: "20", x2: "5", y2: "20" },
100
+ { x1: "15", y1: "4", x2: "9", y2: "20" },
101
+ ],
102
+ underline: [{ d: "M6 4v6a6 6 0 0 0 12 0V4" }, { x1: "4", y1: "20", x2: "20", y2: "20" }],
103
+ strike: [
104
+ { d: "M16 4H9a3 3 0 0 0-2.83 4" },
105
+ { d: "M14 12a4 4 0 0 1 0 8H6" },
106
+ { x1: "4", y1: "12", x2: "20", y2: "12" },
107
+ ],
108
+ alignLeft: [{ d: "M21 5H3" }, { d: "M15 12H3" }, { d: "M17 19H3" }],
109
+ alignCenter: [{ d: "M21 5H3" }, { d: "M17 12H7" }, { d: "M19 19H5" }],
110
+ alignRight: [{ d: "M21 5H3" }, { d: "M21 12H9" }, { d: "M21 19H7" }],
111
+ list: [
112
+ { d: "M3 5h.01" },
113
+ { d: "M3 12h.01" },
114
+ { d: "M3 19h.01" },
115
+ { d: "M8 5h13" },
116
+ { d: "M8 12h13" },
117
+ { d: "M8 19h13" },
118
+ ],
119
+ grid: [
120
+ { x: "3", y: "3", width: "7", height: "7", rx: "1" },
121
+ { x: "14", y: "3", width: "7", height: "7", rx: "1" },
122
+ { x: "14", y: "14", width: "7", height: "7", rx: "1" },
123
+ { x: "3", y: "14", width: "7", height: "7", rx: "1" },
124
+ ],
125
+ bell: [
126
+ { d: "M10.268 21a2 2 0 0 0 3.464 0" },
127
+ {
128
+ d: "M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326",
129
+ },
130
+ ],
131
+ settings: [
132
+ {
133
+ d: "M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915",
134
+ },
135
+ { cx: "12", cy: "12", r: "3" },
136
+ ],
137
+ user: [{ d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }, { cx: "12", cy: "7", r: "4" }],
138
+ users: [
139
+ { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" },
140
+ { d: "M16 3.128a4 4 0 0 1 0 7.744" },
141
+ { d: "M22 21v-2a4 4 0 0 0-3-3.87" },
142
+ { cx: "9", cy: "7", r: "4" },
143
+ ],
144
+ send: [
145
+ {
146
+ d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z",
147
+ },
148
+ { d: "m21.854 2.147-10.94 10.939" },
149
+ ],
150
+ link: [
151
+ { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" },
152
+ { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" },
153
+ ],
154
+ eye: [
155
+ {
156
+ d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",
157
+ },
158
+ { cx: "12", cy: "12", r: "3" },
159
+ ],
160
+ lock: [
161
+ { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" },
162
+ { d: "M7 11V7a5 5 0 0 1 10 0v4" },
163
+ ],
164
+ refresh: [
165
+ { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" },
166
+ { d: "M21 3v5h-5" },
167
+ { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" },
168
+ { d: "M8 16H3v5" },
169
+ ],
170
+ home: [
171
+ { d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" },
172
+ {
173
+ d: "M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",
174
+ },
175
+ ],
176
+ mail: [
177
+ { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" },
178
+ { x: "2", y: "4", width: "20", height: "16", rx: "2" },
179
+ ],
180
+ play: [
181
+ { d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" },
182
+ ],
183
+ pause: [
184
+ { x: "14", y: "3", width: "5", height: "18", rx: "1" },
185
+ { x: "5", y: "3", width: "5", height: "18", rx: "1" },
186
+ ],
187
+ filter: [
188
+ {
189
+ d: "M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z",
190
+ },
191
+ ],
192
+ image: [
193
+ { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" },
194
+ { cx: "9", cy: "9", r: "2" },
195
+ { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" },
196
+ ],
197
+ code: [{ d: "m16 18 6-6-6-6" }, { d: "m8 6-6 6 6 6" }],
198
+ undo: [{ d: "M9 14 4 9l5-5" }, { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" }],
199
+ redo: [{ d: "m15 14 5-5-5-5" }, { d: "M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13" }],
200
+ pin: [
201
+ { d: "M12 17v5" },
202
+ {
203
+ d: "M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z",
204
+ },
205
+ ],
206
+ sparkle: [
207
+ {
208
+ d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",
209
+ },
210
+ { d: "M20 2v4" },
211
+ { d: "M22 4h-4" },
212
+ { cx: "4", cy: "20", r: "2" },
213
+ ],
214
+ seat: [
215
+ { d: "M19 9V6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v3" },
216
+ {
217
+ d: "M3 16a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z",
218
+ },
219
+ { d: "M5 18v2" },
220
+ { d: "M19 18v2" },
221
+ ],
222
+ chat: [
223
+ {
224
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z",
225
+ },
226
+ ],
227
+ info: [{ cx: "12", cy: "12", r: "10" }, { d: "M12 16v-4" }, { d: "M12 8h.01" }],
228
+ panelRight: [{ x: "3", y: "3", width: "18", height: "18", rx: "2" }, { d: "M15 3v18" }],
229
+ creditCard: [{ x: "2", y: "5", width: "20", height: "14", rx: "2" }, { d: "M2 10h20" }],
230
+ tag: [
231
+ {
232
+ d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",
233
+ },
234
+ { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" },
235
+ ],
236
+ archive: [
237
+ { x: "2", y: "3", width: "20", height: "5", rx: "1" },
238
+ { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" },
239
+ { d: "M10 12h4" },
240
+ ],
241
+ signOut: [
242
+ { d: "m16 17 5-5-5-5" },
243
+ { d: "M21 12H9" },
244
+ { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" },
245
+ ],
246
+ sun: [
247
+ { cx: "12", cy: "12", r: "4" },
248
+ { d: "M12 2v2" },
249
+ { d: "M12 20v2" },
250
+ { d: "m4.93 4.93 1.41 1.41" },
251
+ { d: "m17.66 17.66 1.41 1.41" },
252
+ { d: "M2 12h2" },
253
+ { d: "M20 12h2" },
254
+ { d: "m6.34 17.66-1.41 1.41" },
255
+ { d: "m19.07 4.93-1.41 1.41" },
256
+ ],
257
+ moon: [
258
+ {
259
+ d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",
260
+ },
261
+ ],
262
+ } satisfies Record<string, readonly GlyphMark[]>;
263
+
264
+ export type GlyphName = keyof typeof MARKS;
265
+
266
+ export interface GlyphProps extends Omit<SVGProps<SVGSVGElement>, "name"> {
267
+ name: GlyphName;
268
+ /** Rendered size in px. Defaults to 1em so the glyph tracks the text around it. */
269
+ size?: number | string;
270
+ }
271
+
272
+ function Mark({ mark }: { mark: GlyphMark }) {
273
+ if ("d" in mark) return <path d={mark.d} />;
274
+ if ("x1" in mark) return <line x1={mark.x1} y1={mark.y1} x2={mark.x2} y2={mark.y2} />;
275
+ if ("width" in mark) {
276
+ return (
277
+ <rect
278
+ x={mark.x}
279
+ y={mark.y}
280
+ width={mark.width}
281
+ height={mark.height}
282
+ rx={mark.rx}
283
+ ry={mark.ry}
284
+ />
285
+ );
286
+ }
287
+ return <circle cx={mark.cx} cy={mark.cy} r={mark.r} fill={mark.fill} stroke={mark.stroke} />;
288
+ }
289
+
290
+ /** Small line icons used inside design-system controls. Decorative by default. */
291
+ export function Glyph({ name, size = "1em", className, ...props }: GlyphProps) {
292
+ return (
293
+ <svg
294
+ viewBox="0 0 24 24"
295
+ width={size}
296
+ height={size}
297
+ fill="none"
298
+ stroke="currentColor"
299
+ strokeWidth={2}
300
+ strokeLinecap="round"
301
+ strokeLinejoin="round"
302
+ aria-hidden
303
+ focusable={false}
304
+ className={["os-glyph", className].filter(Boolean).join(" ")}
305
+ {...props}
306
+ >
307
+ {MARKS[name].map((mark, index) => (
308
+ <Mark key={index} mark={mark} />
309
+ ))}
310
+ </svg>
311
+ );
312
+ }
313
+
314
+ type IconComponent = ((props: SVGProps<SVGSVGElement>) => React.JSX.Element) & {
315
+ displayName?: string;
316
+ };
317
+
318
+ /**
319
+ * Every glyph as an Astryx `IconType`, so it drops straight into
320
+ * `<Icon icon={icons.plus} />`, Button `icon`, menus, and toggles.
321
+ */
322
+ export const icons = Object.fromEntries(
323
+ (Object.keys(MARKS) as GlyphName[]).map((name) => {
324
+ const Component: IconComponent = ({ name: _ignored, ...props }) => (
325
+ <Glyph {...props} name={name} />
326
+ );
327
+ Component.displayName = `Icon(${name})`;
328
+ return [name, Component];
329
+ }),
330
+ ) as Record<GlyphName, IconComponent>;
@@ -0,0 +1,67 @@
1
+ import { Button } from "@astryxdesign/core/Button";
2
+
3
+ export interface GoogleMarkProps {
4
+ /** Width and height in pixels. */
5
+ size?: number;
6
+ }
7
+
8
+ /** Google's "G", for buttons that sign in with Google. */
9
+ export function GoogleMark({ size = 18 }: GoogleMarkProps) {
10
+ return (
11
+ <svg
12
+ className="os-google-mark"
13
+ width={size}
14
+ height={size}
15
+ viewBox="0 0 48 48"
16
+ aria-hidden="true"
17
+ focusable="false"
18
+ >
19
+ <path
20
+ fill="var(--os-google-red)"
21
+ d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
22
+ />
23
+ <path
24
+ fill="var(--os-google-blue)"
25
+ d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
26
+ />
27
+ <path
28
+ fill="var(--os-google-yellow)"
29
+ d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
30
+ />
31
+ <path
32
+ fill="var(--os-google-green)"
33
+ d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
34
+ />
35
+ </svg>
36
+ );
37
+ }
38
+
39
+ export interface GoogleSignInButtonProps {
40
+ /** The app's route that starts the trip to Google. */
41
+ action: string;
42
+ /** Where to land after signing in. */
43
+ next?: string;
44
+ /** The kind of account a sign-up creates, sent as the form's "mode" field. */
45
+ mode?: string;
46
+ /** @default "Continue with Google" */
47
+ label?: string;
48
+ }
49
+
50
+ /**
51
+ * "Continue with Google". It posts a form rather than linking, so nothing can
52
+ * prefetch the route and start a sign-in on its own.
53
+ */
54
+ export function GoogleSignInButton({
55
+ action,
56
+ next = "",
57
+ mode,
58
+ label = "Continue with Google",
59
+ }: GoogleSignInButtonProps) {
60
+ return (
61
+ <form method="post" action={action}>
62
+ <input type="hidden" name="next" value={next} />
63
+ {mode ? <input type="hidden" name="mode" value={mode} /> : null}
64
+ <Button type="submit" label={label} icon={<GoogleMark />} width="100%" />
65
+ </form>
66
+ );
67
+ }
@@ -0,0 +1,131 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ type CSSProperties,
7
+ type ElementType,
8
+ type ReactNode,
9
+ } from "react";
10
+
11
+ import { TIERS, spacing, type ResponsiveTo, type SpacingStep, type Tier } from "./breakpoints";
12
+
13
+ export const GRID_COLUMNS = 12;
14
+
15
+ /** A number of tracks, the full row, or hidden at this tier. */
16
+ export type GridSpanValue = number | "full" | "hidden";
17
+
18
+ /** One value, or a value per tier. Unset tiers inherit from the tier below. */
19
+ export type Responsive<T> = T | ({ base?: T } & Partial<Record<Tier, T>>);
20
+
21
+ export interface GridSystemProps {
22
+ children: ReactNode;
23
+ /** Track count. */
24
+ columns?: number;
25
+ gap?: SpacingStep;
26
+ /** Row gap when it should differ from the column gap. */
27
+ rowGap?: SpacingStep;
28
+ /** container (default) reflows by the grid's own width; viewport by the window. */
29
+ responsiveTo?: ResponsiveTo;
30
+ /** Vertical alignment of items within their row. */
31
+ align?: "start" | "center" | "end" | "stretch";
32
+ /** Backfill holes left by wide items. */
33
+ dense?: boolean;
34
+ as?: ElementType;
35
+ }
36
+
37
+ export interface GridColumnProps extends Partial<Record<Tier, GridSpanValue>> {
38
+ children?: ReactNode;
39
+ /** Span below the first tier. */
40
+ span?: GridSpanValue;
41
+ /** 1-based starting track, per tier if needed. */
42
+ start?: Responsive<number>;
43
+ /** Visual order, per tier if needed. */
44
+ order?: Responsive<number>;
45
+ /** Rows to cover. */
46
+ rowSpan?: number;
47
+ as?: ElementType;
48
+ className?: string;
49
+ }
50
+
51
+ const ColumnsContext = createContext(GRID_COLUMNS);
52
+
53
+ function perTier<T>(value: Responsive<T> | undefined): Partial<Record<"base" | Tier, T>> {
54
+ if (value == null) return {};
55
+ if (typeof value === "object") return value;
56
+ return { base: value };
57
+ }
58
+
59
+ /**
60
+ * A 12-track grid whose columns reflow per tier — the Joined answer to
61
+ * "full width on phones, halves on tablets, thirds on desktop". For
62
+ * intrinsic, count-free reflow, use Astryx `Grid columns={{ minWidth }}`.
63
+ */
64
+ export function GridSystem({
65
+ children,
66
+ columns = GRID_COLUMNS,
67
+ gap = 4,
68
+ rowGap,
69
+ responsiveTo = "container",
70
+ align = "stretch",
71
+ dense,
72
+ as: Tag = "div",
73
+ }: GridSystemProps) {
74
+ const style = {
75
+ "--os-gs-columns": columns,
76
+ "--os-gs-gap": spacing(gap),
77
+ "--os-gs-row-gap": spacing(rowGap ?? gap),
78
+ "--os-gs-align": align,
79
+ } as CSSProperties;
80
+
81
+ return (
82
+ <ColumnsContext.Provider value={columns}>
83
+ <div className={`os-gs os-responsive-${responsiveTo}`}>
84
+ <Tag className={dense ? "os-gs-grid os-gs-dense" : "os-gs-grid"} style={style}>
85
+ {children}
86
+ </Tag>
87
+ </div>
88
+ </ColumnsContext.Provider>
89
+ );
90
+ }
91
+
92
+ /** One cell. `span`, then `sm` → `xl` override it as the grid widens. */
93
+ export function GridColumn({
94
+ children,
95
+ span,
96
+ start,
97
+ order,
98
+ rowSpan,
99
+ as: Tag = "div",
100
+ className,
101
+ ...tiers
102
+ }: GridColumnProps) {
103
+ const columns = useContext(ColumnsContext);
104
+ const vars: Record<string, string | number> = {};
105
+
106
+ const spans: Partial<Record<"base" | Tier, GridSpanValue>> = { base: span ?? columns };
107
+ for (const tier of TIERS) if (tiers[tier] != null) spans[tier] = tiers[tier];
108
+ for (const [tier, value] of Object.entries(spans)) {
109
+ const suffix = tier === "base" ? "" : `-${tier}`;
110
+ if (value === "hidden") vars[`--os-gs-display${suffix}`] = "none";
111
+ else {
112
+ vars[`--os-gs-display${suffix}`] = "block";
113
+ vars[`--os-gs-span${suffix}`] =
114
+ value === "full" ? columns : Math.min(columns, Math.max(1, value));
115
+ }
116
+ }
117
+ for (const [tier, value] of Object.entries(perTier(start)))
118
+ vars[`--os-gs-start${tier === "base" ? "" : `-${tier}`}`] = value!;
119
+ for (const [tier, value] of Object.entries(perTier(order)))
120
+ vars[`--os-gs-order${tier === "base" ? "" : `-${tier}`}`] = value!;
121
+ if (rowSpan) vars["--os-gs-rows"] = rowSpan;
122
+
123
+ return (
124
+ <Tag
125
+ className={["os-gs-col", className].filter(Boolean).join(" ")}
126
+ style={vars as CSSProperties}
127
+ >
128
+ {children}
129
+ </Tag>
130
+ );
131
+ }
@@ -0,0 +1,167 @@
1
+ "use client";
2
+
3
+ import { useState, type CSSProperties } from "react";
4
+
5
+ import { heatLevel } from "./chartMath";
6
+ import { ChartTip } from "./chartParts";
7
+
8
+ export type HeatmapDay = {
9
+ /** Local calendar date, `YYYY-MM-DD`. */
10
+ date: string;
11
+ value: number;
12
+ };
13
+
14
+ const LEVELS = 4;
15
+ const DAYS_IN_WEEK = 7;
16
+ const DAY_MS = 86_400_000;
17
+ /** Past this date the first column skips its month name; the next month's would crowd it. */
18
+ const LATE_IN_MONTH = 21;
19
+ const WEEKDAY_LABELS = ["", "Mon", "", "Wed", "", "Fri", ""];
20
+
21
+ function parseDay(iso: string) {
22
+ const [year, month, day] = iso.split("-").map(Number);
23
+ return new Date(year, (month ?? 1) - 1, day ?? 1);
24
+ }
25
+
26
+ function dayKey(date: Date) {
27
+ const month = String(date.getMonth() + 1).padStart(2, "0");
28
+ const day = String(date.getDate()).padStart(2, "0");
29
+ return `${date.getFullYear()}-${month}-${day}`;
30
+ }
31
+
32
+ /**
33
+ * Daily activity as weeks of cells, darker for busier days — one hue, light to dark.
34
+ * Weeks run left to right, Sunday on top. Each cell reads out its date and count.
35
+ */
36
+ export function HeatmapCalendar({
37
+ days,
38
+ label,
39
+ unit = "events",
40
+ locale,
41
+ }: {
42
+ /** One entry per day with activity; missing days count as zero. */
43
+ days: HeatmapDay[];
44
+ /** Accessible name for the grid. */
45
+ label: string;
46
+ /** Plural noun read after each count, e.g. "applications". */
47
+ unit?: string;
48
+ locale?: string;
49
+ }) {
50
+ const [active, setActive] = useState<string | null>(null);
51
+ if (days.length === 0) return null;
52
+
53
+ const values = new Map(days.map((day) => [day.date, day.value]));
54
+ const sorted = [...days].sort((a, b) => a.date.localeCompare(b.date));
55
+ const first = parseDay(sorted[0].date);
56
+ const last = parseDay(sorted[sorted.length - 1].date);
57
+ const start = new Date(first.getFullYear(), first.getMonth(), first.getDate() - first.getDay());
58
+ const total = Math.round((last.getTime() - start.getTime()) / DAY_MS) + 1;
59
+ const weeks = Math.ceil(total / DAYS_IN_WEEK);
60
+ const max = Math.max(0, ...days.map((day) => day.value));
61
+ const month = new Intl.DateTimeFormat(locale, { month: "short" });
62
+ const full = new Intl.DateTimeFormat(locale, {
63
+ weekday: "short",
64
+ month: "short",
65
+ day: "numeric",
66
+ });
67
+
68
+ const columns = Array.from({ length: weeks }, (_, week) =>
69
+ Array.from({ length: DAYS_IN_WEEK }, (_, weekday) => {
70
+ const date = new Date(
71
+ start.getFullYear(),
72
+ start.getMonth(),
73
+ start.getDate() + week * 7 + weekday,
74
+ );
75
+ const key = dayKey(date);
76
+ const inRange = date >= first && date <= last;
77
+ return { key, date, inRange, value: values.get(key) ?? 0 };
78
+ }),
79
+ );
80
+
81
+ return (
82
+ <div className="os-heatmap">
83
+ <div
84
+ className="os-heatmap-grid"
85
+ role="group"
86
+ aria-label={label}
87
+ style={{ "--os-heat-weeks": weeks } as CSSProperties}
88
+ >
89
+ <span />
90
+ {columns.map((column, week) => {
91
+ const opener = column.find((cell) => cell.inRange && cell.date.getDate() === 1);
92
+ const shown =
93
+ opener?.date ?? (week === 0 && first.getDate() <= LATE_IN_MONTH ? first : null);
94
+ return (
95
+ <span key={`m-${week}`} className="os-heatmap-month">
96
+ {shown ? month.format(shown) : ""}
97
+ </span>
98
+ );
99
+ })}
100
+ {WEEKDAY_LABELS.map((weekday, row) => (
101
+ <HeatmapRow
102
+ key={row}
103
+ weekday={weekday}
104
+ cells={columns.map((column) => column[row])}
105
+ max={max}
106
+ active={active}
107
+ onActive={setActive}
108
+ describe={(cell) => `${full.format(cell.date)}: ${cell.value} ${unit}`}
109
+ />
110
+ ))}
111
+ </div>
112
+ <div className="os-heatmap-scale" aria-hidden>
113
+ <span>Less</span>
114
+ {Array.from({ length: LEVELS + 1 }, (_, level) => (
115
+ <span key={level} className="os-heatmap-cell" data-level={level} />
116
+ ))}
117
+ <span>More</span>
118
+ </div>
119
+ </div>
120
+ );
121
+ }
122
+
123
+ type Cell = { key: string; date: Date; inRange: boolean; value: number };
124
+
125
+ function HeatmapRow({
126
+ weekday,
127
+ cells,
128
+ max,
129
+ active,
130
+ onActive,
131
+ describe,
132
+ }: {
133
+ weekday: string;
134
+ cells: Cell[];
135
+ max: number;
136
+ active: string | null;
137
+ onActive: (key: string | null) => void;
138
+ describe: (cell: Cell) => string;
139
+ }) {
140
+ return (
141
+ <>
142
+ <span className="os-heatmap-weekday">{weekday}</span>
143
+ {cells.map((cell) =>
144
+ cell.inRange ? (
145
+ <span
146
+ key={cell.key}
147
+ className="os-heatmap-cell"
148
+ data-level={heatLevel(cell.value, max, LEVELS)}
149
+ tabIndex={0}
150
+ role="img"
151
+ aria-label={describe(cell)}
152
+ onPointerEnter={() => onActive(cell.key)}
153
+ onPointerLeave={() => onActive(null)}
154
+ onFocus={() => onActive(cell.key)}
155
+ onBlur={() => onActive(null)}
156
+ >
157
+ {active === cell.key ? (
158
+ <ChartTip left="50%" top={0} rows={[{ label: "", value: describe(cell) }]} />
159
+ ) : null}
160
+ </span>
161
+ ) : (
162
+ <span key={cell.key} className="os-heatmap-cell" data-empty />
163
+ ),
164
+ )}
165
+ </>
166
+ );
167
+ }