@xaroth.nl/design 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 (193) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/all.css +2536 -0
  4. package/dist/astro.d.ts +41 -0
  5. package/dist/astro.js +41 -0
  6. package/dist/bem.d.ts +12 -0
  7. package/dist/bem.js +19 -0
  8. package/dist/components/alert/alert.astro +46 -0
  9. package/dist/components/alert/alert.css +68 -0
  10. package/dist/components/alert/alert.d.ts +11 -0
  11. package/dist/components/alert/alert.js +6 -0
  12. package/dist/components/alert/index.d.ts +14 -0
  13. package/dist/components/alert/index.js +27 -0
  14. package/dist/components/avatar/avatar.astro +23 -0
  15. package/dist/components/avatar/avatar.css +35 -0
  16. package/dist/components/avatar/avatar.d.ts +5 -0
  17. package/dist/components/avatar/avatar.js +7 -0
  18. package/dist/components/avatar/index.d.ts +32 -0
  19. package/dist/components/avatar/index.js +24 -0
  20. package/dist/components/badge/badge.astro +11 -0
  21. package/dist/components/badge/badge.css +46 -0
  22. package/dist/components/badge/badge.d.ts +5 -0
  23. package/dist/components/badge/badge.js +6 -0
  24. package/dist/components/badge/index.d.ts +6 -0
  25. package/dist/components/badge/index.js +3 -0
  26. package/dist/components/breadcrumbs/breadcrumbs.astro +27 -0
  27. package/dist/components/breadcrumbs/breadcrumbs.css +33 -0
  28. package/dist/components/breadcrumbs/breadcrumbs.d.ts +9 -0
  29. package/dist/components/breadcrumbs/breadcrumbs.js +11 -0
  30. package/dist/components/breadcrumbs/index.d.ts +37 -0
  31. package/dist/components/breadcrumbs/index.js +16 -0
  32. package/dist/components/button/button.astro +36 -0
  33. package/dist/components/button/button.css +117 -0
  34. package/dist/components/button/button.d.ts +18 -0
  35. package/dist/components/button/button.js +23 -0
  36. package/dist/components/button/index.d.ts +59 -0
  37. package/dist/components/button/index.js +26 -0
  38. package/dist/components/card/card.astro +98 -0
  39. package/dist/components/card/card.css +91 -0
  40. package/dist/components/card/card.d.ts +19 -0
  41. package/dist/components/card/card.js +7 -0
  42. package/dist/components/card/index.d.ts +11 -0
  43. package/dist/components/card/index.js +7 -0
  44. package/dist/components/description-list/description-list.astro +39 -0
  45. package/dist/components/description-list/description-list.css +72 -0
  46. package/dist/components/description-list/description-list.d.ts +7 -0
  47. package/dist/components/description-list/description-list.js +6 -0
  48. package/dist/components/description-list/index.d.ts +12 -0
  49. package/dist/components/description-list/index.js +5 -0
  50. package/dist/components/divider/divider.astro +27 -0
  51. package/dist/components/divider/divider.css +40 -0
  52. package/dist/components/divider/divider.d.ts +5 -0
  53. package/dist/components/divider/divider.js +11 -0
  54. package/dist/components/divider/index.d.ts +22 -0
  55. package/dist/components/divider/index.js +11 -0
  56. package/dist/components/empty-state/empty-state.astro +47 -0
  57. package/dist/components/empty-state/empty-state.css +38 -0
  58. package/dist/components/empty-state/empty-state.d.ts +12 -0
  59. package/dist/components/empty-state/empty-state.js +7 -0
  60. package/dist/components/empty-state/index.d.ts +5 -0
  61. package/dist/components/empty-state/index.js +3 -0
  62. package/dist/components/field/checkbox-group.astro +58 -0
  63. package/dist/components/field/checkbox.astro +17 -0
  64. package/dist/components/field/field.astro +51 -0
  65. package/dist/components/field/field.css +336 -0
  66. package/dist/components/field/field.d.ts +47 -0
  67. package/dist/components/field/field.js +51 -0
  68. package/dist/components/field/index.d.ts +53 -0
  69. package/dist/components/field/index.js +54 -0
  70. package/dist/components/field/input.astro +27 -0
  71. package/dist/components/field/radio-group.astro +58 -0
  72. package/dist/components/field/radio.astro +17 -0
  73. package/dist/components/field/select.astro +13 -0
  74. package/dist/components/field/switch.astro +17 -0
  75. package/dist/components/field/textarea.astro +24 -0
  76. package/dist/components/filters/filter-layout.astro +72 -0
  77. package/dist/components/filters/filter-panel.astro +13 -0
  78. package/dist/components/filters/filters.css +230 -0
  79. package/dist/components/filters/filters.d.ts +20 -0
  80. package/dist/components/filters/filters.js +24 -0
  81. package/dist/components/filters/index.d.ts +27 -0
  82. package/dist/components/filters/index.js +25 -0
  83. package/dist/components/footer/footer.astro +70 -0
  84. package/dist/components/footer/footer.css +128 -0
  85. package/dist/components/footer/footer.d.ts +16 -0
  86. package/dist/components/footer/footer.js +7 -0
  87. package/dist/components/footer/index.d.ts +35 -0
  88. package/dist/components/footer/index.js +18 -0
  89. package/dist/components/header/header.astro +89 -0
  90. package/dist/components/header/header.css +163 -0
  91. package/dist/components/header/header.d.ts +18 -0
  92. package/dist/components/header/header.js +9 -0
  93. package/dist/components/header/index.d.ts +39 -0
  94. package/dist/components/header/index.js +25 -0
  95. package/dist/components/icon/icon.astro +21 -0
  96. package/dist/components/icon/icon.css +15 -0
  97. package/dist/components/icon/icon.d.ts +5 -0
  98. package/dist/components/icon/icon.js +6 -0
  99. package/dist/components/icon/index.d.ts +57 -0
  100. package/dist/components/icon/index.js +72 -0
  101. package/dist/components/layout/container.astro +15 -0
  102. package/dist/components/layout/grid.astro +14 -0
  103. package/dist/components/layout/index.d.ts +33 -0
  104. package/dist/components/layout/index.js +13 -0
  105. package/dist/components/layout/layout.css +262 -0
  106. package/dist/components/layout/layout.d.ts +24 -0
  107. package/dist/components/layout/layout.js +17 -0
  108. package/dist/components/layout/section-head.astro +42 -0
  109. package/dist/components/layout/section.astro +15 -0
  110. package/dist/components/page-head/index.d.ts +18 -0
  111. package/dist/components/page-head/index.js +14 -0
  112. package/dist/components/page-head/page-head.astro +60 -0
  113. package/dist/components/page-head/page-head.css +79 -0
  114. package/dist/components/page-head/page-head.d.ts +18 -0
  115. package/dist/components/page-head/page-head.js +8 -0
  116. package/dist/components/pagination/index.d.ts +73 -0
  117. package/dist/components/pagination/index.js +87 -0
  118. package/dist/components/pagination/pagination.astro +81 -0
  119. package/dist/components/pagination/pagination.css +70 -0
  120. package/dist/components/pagination/pagination.d.ts +8 -0
  121. package/dist/components/pagination/pagination.js +19 -0
  122. package/dist/components/panel/index.d.ts +11 -0
  123. package/dist/components/panel/index.js +3 -0
  124. package/dist/components/panel/panel.astro +15 -0
  125. package/dist/components/panel/panel.css +48 -0
  126. package/dist/components/panel/panel.d.ts +7 -0
  127. package/dist/components/panel/panel.js +6 -0
  128. package/dist/components/progress/index.d.ts +37 -0
  129. package/dist/components/progress/index.js +44 -0
  130. package/dist/components/progress/progress.astro +18 -0
  131. package/dist/components/progress/progress.css +115 -0
  132. package/dist/components/progress/progress.d.ts +5 -0
  133. package/dist/components/progress/progress.js +7 -0
  134. package/dist/components/prose/code-block.astro +26 -0
  135. package/dist/components/prose/index.d.ts +14 -0
  136. package/dist/components/prose/index.js +67 -0
  137. package/dist/components/prose/prose.astro +14 -0
  138. package/dist/components/prose/prose.css +156 -0
  139. package/dist/components/prose/prose.d.ts +12 -0
  140. package/dist/components/prose/prose.js +11 -0
  141. package/dist/components/stat/index.d.ts +15 -0
  142. package/dist/components/stat/index.js +5 -0
  143. package/dist/components/stat/stat-group.astro +11 -0
  144. package/dist/components/stat/stat.astro +43 -0
  145. package/dist/components/stat/stat.css +51 -0
  146. package/dist/components/stat/stat.d.ts +15 -0
  147. package/dist/components/stat/stat.js +10 -0
  148. package/dist/components/table/index.d.ts +38 -0
  149. package/dist/components/table/index.js +18 -0
  150. package/dist/components/table/table.astro +111 -0
  151. package/dist/components/table/table.css +78 -0
  152. package/dist/components/table/table.d.ts +11 -0
  153. package/dist/components/table/table.js +10 -0
  154. package/dist/components/tabs/index.d.ts +21 -0
  155. package/dist/components/tabs/index.js +13 -0
  156. package/dist/components/tabs/tabs.astro +30 -0
  157. package/dist/components/tabs/tabs.css +59 -0
  158. package/dist/components/tabs/tabs.d.ts +9 -0
  159. package/dist/components/tabs/tabs.js +6 -0
  160. package/dist/components/tag/index.d.ts +19 -0
  161. package/dist/components/tag/index.js +7 -0
  162. package/dist/components/tag/tag.astro +17 -0
  163. package/dist/components/tag/tag.css +30 -0
  164. package/dist/components/tag/tag.d.ts +9 -0
  165. package/dist/components/tag/tag.js +12 -0
  166. package/dist/components/timeline/index.d.ts +22 -0
  167. package/dist/components/timeline/index.js +5 -0
  168. package/dist/components/timeline/timeline.astro +40 -0
  169. package/dist/components/timeline/timeline.css +92 -0
  170. package/dist/components/timeline/timeline.d.ts +9 -0
  171. package/dist/components/timeline/timeline.js +7 -0
  172. package/dist/components/tooltip/index.d.ts +15 -0
  173. package/dist/components/tooltip/index.js +41 -0
  174. package/dist/components/tooltip/tooltip.astro +25 -0
  175. package/dist/components/tooltip/tooltip.css +84 -0
  176. package/dist/components/tooltip/tooltip.d.ts +12 -0
  177. package/dist/components/tooltip/tooltip.js +34 -0
  178. package/dist/config.d.ts +9 -0
  179. package/dist/config.js +6 -0
  180. package/dist/env.d.ts +2 -0
  181. package/dist/html.d.ts +37 -0
  182. package/dist/html.js +126 -0
  183. package/dist/parts.d.ts +27 -0
  184. package/dist/parts.js +28 -0
  185. package/dist/react.d.ts +27 -0
  186. package/dist/react.js +28 -0
  187. package/dist/scss/_chamfer.scss +37 -0
  188. package/dist/scss/_layers.scss +2 -0
  189. package/dist/scss/_theme.scss +15 -0
  190. package/dist/themes/eve-online.css +1945 -0
  191. package/dist/themes/xaroth.css +1974 -0
  192. package/dist/themes.css +3919 -0
  193. package/package.json +80 -0
@@ -0,0 +1,336 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-field,
4
+ .x-choice-group,
5
+ .x-input,
6
+ .x-input-wrap,
7
+ .x-textarea,
8
+ .x-select,
9
+ .x-checkbox,
10
+ .x-radio,
11
+ .x-switch {
12
+ --x-field-font: var(--x-font-ui);
13
+ --x-field-bg: var(--x-color-bg-sunk);
14
+ --x-field-bg-focus: var(--x-field-bg);
15
+ --x-field-bg-disabled: var(--x-color-surface);
16
+ --x-field-bg-readonly: transparent;
17
+ --x-field-edge-readonly: var(--x-color-field-edge);
18
+ --x-field-edge-hover: var(--x-color-text-faint);
19
+ --x-field-glow: none;
20
+ --x-field-label-font: var(--x-font-ui);
21
+ --x-field-label-weight: 600;
22
+ --x-field-label-tracking: var(--x-track-label);
23
+ --x-field-label-case: uppercase;
24
+ --x-field-help-fs: var(--x-fs-label);
25
+ --x-field-arrow: var(--x-color-accent);
26
+ --x-check-size: 18px;
27
+ --x-check-mark: calc(var(--x-check-size) - 8px);
28
+ --x-check-edge: var(--x-color-field-edge);
29
+ --x-switch-width: 40px;
30
+ --x-switch-height: 22px;
31
+ --x-switch-knob: 14px;
32
+ --x-switch-knob-off: var(--x-color-text-faint);
33
+ --x-check-on-edge: var(--x-color-accent);
34
+ --x-check-on-bg: var(--x-field-bg);
35
+ --x-check-on-glow: none;
36
+ --x-check-ink: var(--x-color-accent);
37
+ --x-check-ink-glow: none;
38
+ }
39
+ .x-switch {
40
+ --x-check-on-bg: var(--x-color-accent-wash);
41
+ }
42
+ .x-field {
43
+ display: grid;
44
+ gap: var(--x-space-2);
45
+ align-content: start;
46
+ min-width: 0;
47
+ }
48
+ .x-field__label {
49
+ font-family: var(--x-field-label-font);
50
+ font-size: var(--x-fs-label);
51
+ font-weight: var(--x-field-label-weight);
52
+ line-height: 1.3;
53
+ letter-spacing: var(--x-field-label-tracking);
54
+ text-transform: var(--x-field-label-case);
55
+ color: var(--x-color-text-muted);
56
+ }
57
+ .x-field__required {
58
+ margin-left: 0.25em;
59
+ color: var(--x-color-accent);
60
+ }
61
+ .x-field__help, .x-field__error {
62
+ margin: 0;
63
+ font-family: var(--x-field-font);
64
+ font-size: var(--x-field-help-fs);
65
+ line-height: 1.4;
66
+ }
67
+ .x-field__help {
68
+ color: var(--x-color-text-faint);
69
+ }
70
+ .x-field__error {
71
+ font-weight: 600;
72
+ color: var(--x-color-danger);
73
+ }
74
+ .x-input,
75
+ .x-textarea,
76
+ .x-select {
77
+ display: block;
78
+ width: 100%;
79
+ min-width: 0;
80
+ min-height: var(--x-control-md);
81
+ padding: 0 var(--x-space-3);
82
+ margin: 0;
83
+ font-family: var(--x-field-font);
84
+ font-size: var(--x-fs-ui);
85
+ line-height: 1.4;
86
+ color: var(--x-color-text);
87
+ background-color: var(--x-field-bg);
88
+ border: 1px solid var(--x-color-field-edge);
89
+ border-radius: var(--x-radius);
90
+ transition: border-color var(--x-duration) var(--x-ease), box-shadow var(--x-duration) var(--x-ease), background-color var(--x-duration) var(--x-ease);
91
+ }
92
+ .x-input::placeholder,
93
+ .x-textarea::placeholder,
94
+ .x-select::placeholder {
95
+ color: var(--x-color-text-faint);
96
+ opacity: 1;
97
+ }
98
+ .x-input:hover:not(:disabled, [readonly]),
99
+ .x-textarea:hover:not(:disabled, [readonly]),
100
+ .x-select:hover:not(:disabled, [readonly]) {
101
+ border-color: var(--x-field-edge-hover);
102
+ }
103
+ .x-input[readonly],
104
+ .x-textarea[readonly],
105
+ .x-select[readonly] {
106
+ color: var(--x-color-text-muted);
107
+ background-color: var(--x-field-bg-readonly);
108
+ border-style: dashed;
109
+ border-color: var(--x-field-edge-readonly);
110
+ }
111
+ .x-input:focus-visible:not([readonly]),
112
+ .x-textarea:focus-visible:not([readonly]),
113
+ .x-select:focus-visible:not([readonly]) {
114
+ background-color: var(--x-field-bg-focus);
115
+ }
116
+ .x-input:focus-visible,
117
+ .x-textarea:focus-visible,
118
+ .x-select:focus-visible {
119
+ border-color: var(--x-color-accent);
120
+ outline: var(--x-focus-width) solid var(--x-color-focus);
121
+ outline-offset: var(--x-focus-offset);
122
+ box-shadow: var(--x-field-glow);
123
+ }
124
+ .x-input[aria-invalid=true],
125
+ .x-textarea[aria-invalid=true],
126
+ .x-select[aria-invalid=true] {
127
+ border-color: var(--x-color-danger);
128
+ box-shadow: 0 0 0 1px var(--x-color-danger);
129
+ }
130
+ .x-input:disabled,
131
+ .x-textarea:disabled,
132
+ .x-select:disabled {
133
+ color: var(--x-color-text-faint);
134
+ cursor: not-allowed;
135
+ background-color: var(--x-field-bg-disabled);
136
+ border-color: var(--x-color-line-strong);
137
+ }
138
+ .x-input {
139
+ height: var(--x-control-md);
140
+ }
141
+ .x-input--start {
142
+ padding-left: calc(var(--x-space-3) + 18px + var(--x-space-3));
143
+ }
144
+ .x-input::-webkit-search-cancel-button {
145
+ filter: grayscale(1);
146
+ }
147
+ .x-input-wrap {
148
+ position: relative;
149
+ display: block;
150
+ min-width: 0;
151
+ }
152
+ .x-input-wrap__start {
153
+ position: absolute;
154
+ top: 50%;
155
+ left: var(--x-space-3);
156
+ display: inline-flex;
157
+ color: var(--x-input-icon, var(--x-color-text-faint));
158
+ pointer-events: none;
159
+ transform: translateY(-50%);
160
+ }
161
+ .x-input-wrap__start svg {
162
+ width: 18px;
163
+ height: 18px;
164
+ }
165
+ .x-textarea {
166
+ min-height: var(--x-textarea-min, 8rem);
167
+ padding-block: 10px;
168
+ resize: vertical;
169
+ }
170
+ .x-select {
171
+ height: var(--x-control-md);
172
+ padding-right: 40px;
173
+ appearance: none;
174
+ cursor: pointer;
175
+ background-image: linear-gradient(to top right, transparent calc(50% - 0.8px), var(--x-field-arrow) calc(50% - 0.8px) calc(50% + 0.8px), transparent calc(50% + 0.8px)), linear-gradient(to top left, transparent calc(50% - 0.8px), var(--x-field-arrow) calc(50% - 0.8px) calc(50% + 0.8px), transparent calc(50% + 0.8px));
176
+ background-repeat: no-repeat;
177
+ background-position: right 19px center, right 14px center;
178
+ background-size: 5px 5px;
179
+ }
180
+ .x-select option {
181
+ color: var(--x-color-text);
182
+ background: var(--x-color-surface);
183
+ }
184
+ .x-select:disabled {
185
+ --x-field-arrow: var(--x-color-line-strong);
186
+ }
187
+ .x-checkbox,
188
+ .x-radio,
189
+ .x-switch {
190
+ display: flex;
191
+ gap: var(--x-space-3);
192
+ align-items: center;
193
+ min-height: var(--x-control-sm);
194
+ font-family: var(--x-field-font);
195
+ font-size: var(--x-fs-ui);
196
+ line-height: 1.4;
197
+ color: var(--x-color-text);
198
+ cursor: pointer;
199
+ }
200
+ .x-checkbox:has(:disabled),
201
+ .x-radio:has(:disabled),
202
+ .x-switch:has(:disabled) {
203
+ color: var(--x-color-text-faint);
204
+ cursor: not-allowed;
205
+ }
206
+ .x-checkbox__input,
207
+ .x-radio__input,
208
+ .x-switch__input {
209
+ position: relative;
210
+ display: grid;
211
+ flex: none;
212
+ place-content: center;
213
+ margin: 0;
214
+ appearance: none;
215
+ cursor: inherit;
216
+ background: var(--x-field-bg);
217
+ border: 1px solid var(--x-check-edge);
218
+ box-shadow: var(--x-check-ring, none);
219
+ transition: border-color var(--x-duration) var(--x-ease), background-color var(--x-duration) var(--x-ease), box-shadow var(--x-duration) var(--x-ease);
220
+ }
221
+ .x-checkbox__input::before,
222
+ .x-radio__input::before,
223
+ .x-switch__input::before {
224
+ content: "";
225
+ transition: transform var(--x-duration) var(--x-ease), opacity var(--x-duration) var(--x-ease), background-color var(--x-duration) var(--x-ease);
226
+ }
227
+ .x-checkbox__input:hover:not(:disabled),
228
+ .x-radio__input:hover:not(:disabled),
229
+ .x-switch__input:hover:not(:disabled) {
230
+ border-color: var(--x-check-edge-hover, var(--x-color-accent));
231
+ }
232
+ .x-checkbox__input:checked,
233
+ .x-radio__input:checked,
234
+ .x-switch__input:checked {
235
+ background: var(--x-check-on-bg);
236
+ border-color: var(--x-check-on-edge);
237
+ box-shadow: var(--x-check-ring, var(--x-check-on-glow));
238
+ }
239
+ .x-checkbox__input[aria-invalid=true],
240
+ .x-radio__input[aria-invalid=true],
241
+ .x-switch__input[aria-invalid=true] {
242
+ --x-check-edge: var(--x-color-danger);
243
+ --x-check-edge-hover: var(--x-color-danger);
244
+ --x-check-on-edge: var(--x-color-danger);
245
+ --x-check-on-bg: var(--x-color-danger-wash);
246
+ --x-check-ink: var(--x-color-danger);
247
+ --x-check-ink-glow: none;
248
+ --x-check-ring: 0 0 0 1px var(--x-color-danger);
249
+ }
250
+ .x-checkbox__input:disabled,
251
+ .x-radio__input:disabled,
252
+ .x-switch__input:disabled {
253
+ border-color: var(--x-color-line-strong);
254
+ }
255
+ .x-checkbox__input,
256
+ .x-radio__input {
257
+ width: var(--x-check-size);
258
+ height: var(--x-check-size);
259
+ }
260
+ .x-checkbox__input::before,
261
+ .x-radio__input::before {
262
+ width: var(--x-check-mark);
263
+ height: var(--x-check-mark);
264
+ background: var(--x-check-ink);
265
+ opacity: 0;
266
+ transform: scale(0.4);
267
+ }
268
+ .x-checkbox__input:checked::before,
269
+ .x-radio__input:checked::before {
270
+ box-shadow: var(--x-check-ink-glow);
271
+ opacity: 1;
272
+ transform: none;
273
+ }
274
+ .x-checkbox__input:disabled::before,
275
+ .x-radio__input:disabled::before {
276
+ background: var(--x-color-text-faint);
277
+ }
278
+ .x-checkbox__input {
279
+ border-radius: var(--x-radius);
280
+ }
281
+ .x-radio__input {
282
+ border-radius: 50%;
283
+ }
284
+ .x-radio__input::before {
285
+ border-radius: 50%;
286
+ }
287
+ .x-switch__input {
288
+ width: var(--x-switch-width);
289
+ height: var(--x-switch-height);
290
+ place-content: center start;
291
+ border-radius: var(--x-radius);
292
+ }
293
+ .x-switch__input::before {
294
+ width: var(--x-switch-knob);
295
+ height: var(--x-switch-knob);
296
+ margin-left: calc((var(--x-switch-height) - 2px - var(--x-switch-knob)) / 2);
297
+ background: var(--x-switch-knob-off);
298
+ }
299
+ .x-switch__input:checked::before {
300
+ background: var(--x-check-ink);
301
+ box-shadow: var(--x-check-ink-glow);
302
+ transform: translateX(calc(var(--x-switch-width) - var(--x-switch-height)));
303
+ }
304
+ .x-switch__input:disabled::before {
305
+ background: var(--x-color-line-strong);
306
+ }
307
+ .x-choice-group {
308
+ display: grid;
309
+ gap: var(--x-space-2);
310
+ align-content: start;
311
+ min-width: 0;
312
+ padding: 0;
313
+ margin: 0;
314
+ border: 0;
315
+ }
316
+ .x-choice-group__legend {
317
+ padding: 0;
318
+ margin-bottom: var(--x-space-2);
319
+ font-family: var(--x-field-label-font);
320
+ font-size: var(--x-fs-label);
321
+ font-weight: var(--x-field-label-weight);
322
+ line-height: 1.3;
323
+ letter-spacing: var(--x-field-label-tracking);
324
+ text-transform: var(--x-field-label-case);
325
+ color: var(--x-color-text-muted);
326
+ }
327
+ .x-choice-group__options {
328
+ display: flex;
329
+ flex-direction: column;
330
+ gap: var(--x-space-1);
331
+ }
332
+ .x-choice-group--horizontal .x-choice-group__options {
333
+ flex-flow: row wrap;
334
+ gap: var(--x-space-1) var(--x-space-5);
335
+ }
336
+ }
@@ -0,0 +1,47 @@
1
+ import './field.scss';
2
+ import { type FieldsetHTMLAttributes, type HTMLAttributes, type InputHTMLAttributes, type ReactElement, type ReactNode, type SelectHTMLAttributes, type TextareaHTMLAttributes } from 'react';
3
+ import { type InputType, type ChoiceGroupOrientation } from './index.ts';
4
+ export type FieldProps = Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
5
+ /** Id of the control inside. Help and error text get `${id}-help` and `${id}-error`. */
6
+ id: string;
7
+ label?: ReactNode;
8
+ description?: ReactNode;
9
+ error?: ReactNode;
10
+ required?: boolean;
11
+ /** One control: Input, Textarea, Select, Checkbox, Switch or a native control. Field sets its id and aria. */
12
+ children: ReactElement;
13
+ };
14
+ export declare function Field({ id, label, description, error, required, className, children, ...rest }: FieldProps): import("react").JSX.Element;
15
+ export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> & {
16
+ type?: InputType;
17
+ /** Shown inside the start of the field, usually an icon. Hidden from assistive tech. */
18
+ start?: ReactNode;
19
+ };
20
+ export declare function Input({ type, start, className, ...rest }: InputProps): import("react").JSX.Element;
21
+ export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>;
22
+ export declare function Textarea({ className, ...rest }: TextareaProps): import("react").JSX.Element;
23
+ export type SelectProps = SelectHTMLAttributes<HTMLSelectElement>;
24
+ export declare function Select({ className, children, ...rest }: SelectProps): import("react").JSX.Element;
25
+ export type ChoiceProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'role'> & {
26
+ /** Label text next to the control. */
27
+ children?: ReactNode;
28
+ };
29
+ export type CheckboxProps = ChoiceProps;
30
+ export type RadioProps = ChoiceProps;
31
+ export type SwitchProps = ChoiceProps;
32
+ export declare const Checkbox: (props: CheckboxProps) => import("react").JSX.Element;
33
+ export declare const Radio: (props: RadioProps) => import("react").JSX.Element;
34
+ export declare const Switch: (props: SwitchProps) => import("react").JSX.Element;
35
+ export type ChoiceGroupProps = Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'id' | 'role'> & {
36
+ /** Help and error text get `${id}-help` and `${id}-error`. */
37
+ id: string;
38
+ legend?: ReactNode;
39
+ description?: ReactNode;
40
+ error?: ReactNode;
41
+ required?: boolean;
42
+ orientation?: ChoiceGroupOrientation;
43
+ };
44
+ export type RadioGroupProps = ChoiceGroupProps;
45
+ export type CheckboxGroupProps = ChoiceGroupProps;
46
+ export declare const RadioGroup: (props: RadioGroupProps) => import("react").JSX.Element;
47
+ export declare const CheckboxGroup: (props: CheckboxGroupProps) => import("react").JSX.Element;
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import './field.css';
4
+ import { Children, cloneElement, isValidElement, } from 'react';
5
+ import { defined } from "../../html.js";
6
+ import { choiceClass, fieldClass, fieldControlAttrs, fieldIds, inputClass, mergeDescribedBy, choiceGroupAttrs, choiceGroupClass, selectClass, textareaClass, } from "./index.js";
7
+ // Matches Astro, where an empty string prop counts as absent.
8
+ const present = (node) => node != null && node !== false && node !== '';
9
+ export function Field({ id, label, description, error, required, className, children, ...rest }) {
10
+ const hasDescription = present(description);
11
+ const hasError = present(error);
12
+ const ids = fieldIds(id);
13
+ const attrs = fieldControlAttrs({ id, hasDescription, hasError, required });
14
+ const control = Children.only(children);
15
+ const own = isValidElement(control) ? control.props : {};
16
+ const wired = cloneElement(control, {
17
+ id,
18
+ 'aria-describedby': mergeDescribedBy(attrs['aria-describedby'], own['aria-describedby']),
19
+ 'aria-invalid': attrs['aria-invalid'] ?? own['aria-invalid'],
20
+ required: attrs.required || own.required,
21
+ });
22
+ return (_jsxs("div", { ...rest, className: fieldClass({ invalid: hasError, className }), children: [present(label) && (_jsxs("label", { className: "x-field__label", htmlFor: id, children: [label, required && (_jsx("span", { className: "x-field__required", "aria-hidden": "true", children: "*" }))] })), wired, hasDescription && (_jsx("p", { className: "x-field__help", id: ids.help, children: description })), hasError && (_jsx("p", { className: "x-field__error", id: ids.error, children: error }))] }));
23
+ }
24
+ export function Input({ type = 'text', start, className, ...rest }) {
25
+ const hasStart = start != null;
26
+ const input = (_jsx("input", { ...rest, type: type, className: inputClass({ hasStart, className }) }));
27
+ if (!hasStart) {
28
+ return input;
29
+ }
30
+ return (_jsxs("span", { className: "x-input-wrap", children: [_jsx("span", { className: "x-input-wrap__start", "aria-hidden": "true", children: start }), input] }));
31
+ }
32
+ export function Textarea({ className, ...rest }) {
33
+ return (_jsx("textarea", { ...rest, className: textareaClass({ className }) }));
34
+ }
35
+ export function Select({ className, children, ...rest }) {
36
+ return (_jsx("select", { ...rest, className: selectClass({ className }), children: children }));
37
+ }
38
+ function Choice({ kind, className, children, ...rest }) {
39
+ return (_jsxs("label", { className: choiceClass(kind, { className }), children: [_jsx("input", { ...rest, type: kind === 'radio' ? 'radio' : 'checkbox', ...(kind === 'switch' && { role: 'switch' }), className: `x-${kind}__input` }), _jsx("span", { className: `x-${kind}__label`, children: children })] }));
40
+ }
41
+ export const Checkbox = (props) => (_jsx(Choice, { kind: "checkbox", ...props }));
42
+ export const Radio = (props) => (_jsx(Choice, { kind: "radio", ...props }));
43
+ export const Switch = (props) => (_jsx(Choice, { kind: "switch", ...props }));
44
+ function ChoiceGroup({ kind, id, legend, description, error, required, orientation, className, children, ...rest }) {
45
+ const hasDescription = present(description);
46
+ const hasError = present(error);
47
+ const ids = fieldIds(id);
48
+ return (_jsxs("fieldset", { ...rest, ...defined(choiceGroupAttrs({ id, hasDescription, hasError, required, kind, describedBy: rest['aria-describedby'] })), className: choiceGroupClass({ orientation, invalid: hasError, className }), children: [present(legend) && (_jsxs("legend", { className: "x-choice-group__legend", children: [legend, required && (_jsx("span", { className: "x-field__required", "aria-hidden": "true", children: "*" }))] })), _jsx("div", { className: "x-choice-group__options", children: children }), hasDescription && (_jsx("p", { className: "x-field__help", id: ids.help, children: description })), hasError && (_jsx("p", { className: "x-field__error", id: ids.error, children: error }))] }));
49
+ }
50
+ export const RadioGroup = (props) => (_jsx(ChoiceGroup, { kind: "radio", ...props }));
51
+ export const CheckboxGroup = (props) => (_jsx(ChoiceGroup, { kind: "checkbox", ...props }));
@@ -0,0 +1,53 @@
1
+ export type InputType = 'text' | 'email' | 'search' | 'number' | 'password' | 'url' | 'tel';
2
+ export type ChoiceGroupOrientation = 'vertical' | 'horizontal';
3
+ export declare const fieldIds: (id: string) => {
4
+ help: string;
5
+ error: string;
6
+ };
7
+ export type FieldWiringInput = {
8
+ id: string;
9
+ hasDescription?: boolean;
10
+ hasError?: boolean;
11
+ required?: boolean;
12
+ };
13
+ export declare const fieldControlAttrs: ({ id, hasDescription, hasError, required }: FieldWiringInput) => {
14
+ id: string;
15
+ 'aria-describedby': string | undefined;
16
+ 'aria-invalid': "true" | undefined;
17
+ required: true | undefined;
18
+ };
19
+ export declare const fieldClass: ({ invalid, className }?: {
20
+ invalid?: boolean;
21
+ className?: string;
22
+ }) => string;
23
+ export declare const inputClass: ({ hasStart, className }?: {
24
+ hasStart?: boolean;
25
+ className?: string;
26
+ }) => string;
27
+ export declare const textareaClass: ({ className }?: {
28
+ className?: string;
29
+ }) => string;
30
+ export declare const selectClass: ({ className }?: {
31
+ className?: string;
32
+ }) => string;
33
+ export type ChoiceKind = 'checkbox' | 'radio' | 'switch';
34
+ export declare const choiceClass: (kind: ChoiceKind, { className }?: {
35
+ className?: string;
36
+ }) => string;
37
+ export declare const choiceGroupClass: ({ orientation, invalid, className, }?: {
38
+ orientation?: ChoiceGroupOrientation;
39
+ invalid?: boolean;
40
+ className?: string;
41
+ }) => string;
42
+ export declare const choiceGroupAttrs: ({ id, hasDescription, hasError, required, kind, describedBy: own, }: FieldWiringInput & {
43
+ kind: "radio" | "checkbox";
44
+ describedBy?: string;
45
+ }) => {
46
+ id: string;
47
+ role: "radiogroup" | undefined;
48
+ 'aria-describedby': string | undefined;
49
+ 'aria-invalid': "true" | undefined;
50
+ 'aria-required': "true" | undefined;
51
+ };
52
+ export declare const wireControlHtml: (html: string, attrs: ReturnType<typeof fieldControlAttrs>) => string;
53
+ export declare const mergeDescribedBy: (fieldIdsList: string | undefined, own: string | undefined) => string | undefined;
@@ -0,0 +1,54 @@
1
+ // Form field recipes. Controls are native elements; Field adds label, help and error text around one control and
2
+ // wires the control's id and aria attributes. IDs come from props, never generated, so Astro and React match.
3
+ import { bem } from "../../bem.js";
4
+ import { getAttr, mergeTokens, rewriteTag } from "../../html.js";
5
+ export const fieldIds = (id) => ({ help: `${id}-help`, error: `${id}-error` });
6
+ // Attributes Field puts on its control. Help comes before error, matching reading order.
7
+ export const fieldControlAttrs = ({ id, hasDescription, hasError, required }) => {
8
+ const ids = fieldIds(id);
9
+ const describedBy = mergeTokens(hasDescription && ids.help, hasError && ids.error);
10
+ return {
11
+ id,
12
+ 'aria-describedby': describedBy || undefined,
13
+ 'aria-invalid': hasError ? 'true' : undefined,
14
+ required: required || undefined,
15
+ };
16
+ };
17
+ const field = bem('x-field');
18
+ const input = bem('x-input');
19
+ const textarea = bem('x-textarea');
20
+ const select = bem('x-select');
21
+ const choiceGroup = bem('x-choice-group', { defaults: { orientation: 'vertical' } });
22
+ export const fieldClass = ({ invalid, className } = {}) => field({ invalid }, className);
23
+ export const inputClass = ({ hasStart, className } = {}) => input({ start: hasStart }, className);
24
+ export const textareaClass = ({ className } = {}) => textarea({}, className);
25
+ export const selectClass = ({ className } = {}) => select({}, className);
26
+ export const choiceClass = (kind, { className } = {}) => bem(`x-${kind}`)({}, className);
27
+ export const choiceGroupClass = ({ orientation = 'vertical', invalid, className, } = {}) => choiceGroup({ orientation, invalid }, className);
28
+ // Radio groups are a radiogroup; checkbox groups stay a plain fieldset, which already groups them.
29
+ // `describedBy` is the group's own aria-describedby, kept after the help and error ids.
30
+ export const choiceGroupAttrs = ({ id, hasDescription, hasError, required, kind, describedBy: own, }) => {
31
+ const { 'aria-describedby': describedBy, 'aria-invalid': invalid } = fieldControlAttrs({
32
+ id,
33
+ hasDescription,
34
+ hasError,
35
+ });
36
+ const radio = kind === 'radio';
37
+ return {
38
+ id,
39
+ role: radio ? 'radiogroup' : undefined,
40
+ 'aria-describedby': mergeDescribedBy(describedBy, own),
41
+ 'aria-invalid': radio ? invalid : undefined,
42
+ 'aria-required': radio && required ? 'true' : undefined,
43
+ };
44
+ };
45
+ const controlTags = new Set(['input', 'select', 'textarea']);
46
+ // Field's id wins; the control's own aria-describedby, aria-invalid (without a Field error) and required are kept.
47
+ export const wireControlHtml = (html, attrs) => rewriteTag(html, (tag) => controlTags.has(tag.name), (tag) => ({
48
+ id: attrs.id,
49
+ 'aria-describedby': mergeTokens(attrs['aria-describedby'], getAttr(tag, 'aria-describedby')) || undefined,
50
+ 'aria-invalid': attrs['aria-invalid'],
51
+ required: attrs.required && getAttr(tag, 'required') === undefined ? true : undefined,
52
+ }));
53
+ // Same merge for React, where Field clones its child with these props.
54
+ export const mergeDescribedBy = (fieldIdsList, own) => mergeTokens(fieldIdsList, own) || undefined;
@@ -0,0 +1,27 @@
1
+ ---
2
+ import './field.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { inputClass, type InputType } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'input'>, 'class' | 'type'> & {
7
+ type?: InputType
8
+ class?: string
9
+ }
10
+
11
+ const { type = 'text', class: className, ...rest } = Astro.props
12
+ const hasStart = Astro.slots.has('start')
13
+ const input = { ...rest, type, class: inputClass({ hasStart, className }) }
14
+ ---
15
+
16
+ {
17
+ hasStart ? (
18
+ <span class="x-input-wrap">
19
+ <span class="x-input-wrap__start" aria-hidden="true">
20
+ <slot name="start" />
21
+ </span>
22
+ <input {...input} />
23
+ </span>
24
+ ) : (
25
+ <input {...input} />
26
+ )
27
+ }
@@ -0,0 +1,58 @@
1
+ ---
2
+ import './field.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { defined } from '../../html.js'
5
+ import { choiceGroupAttrs, choiceGroupClass, fieldIds, type ChoiceGroupOrientation } from './index.js'
6
+
7
+ type Props = Omit<HTMLAttributes<'fieldset'>, 'class' | 'id' | 'role'> & {
8
+ /** Help and error text get `${id}-help` and `${id}-error`. */
9
+ id: string
10
+ legend?: string
11
+ description?: string
12
+ error?: string
13
+ required?: boolean
14
+ orientation?: ChoiceGroupOrientation
15
+ class?: string
16
+ }
17
+
18
+ const { id, legend, description, error, required, orientation, class: className, ...rest } = Astro.props
19
+ const hasLegend = Boolean(legend) || Astro.slots.has('legend')
20
+ const hasDescription = Boolean(description) || Astro.slots.has('description')
21
+ const hasError = Boolean(error) || Astro.slots.has('error')
22
+ const ids = fieldIds(id)
23
+ const recipe = choiceGroupAttrs({
24
+ id,
25
+ hasDescription,
26
+ hasError,
27
+ required,
28
+ kind: 'radio',
29
+ describedBy: rest['aria-describedby'] ?? undefined,
30
+ })
31
+ const attrs = { ...rest, ...defined(recipe), class: choiceGroupClass({ orientation, invalid: hasError, className }) }
32
+ ---
33
+
34
+ <fieldset {...attrs}>
35
+ {
36
+ hasLegend && (
37
+ <legend class="x-choice-group__legend">
38
+ {Astro.slots.has('legend') ? <slot name="legend" /> : legend}
39
+ {required && <span class="x-field__required" aria-hidden="true">*</span>}
40
+ </legend>
41
+ )
42
+ }
43
+ <div class="x-choice-group__options"><slot /></div>
44
+ {
45
+ hasDescription && (
46
+ <p class="x-field__help" id={ids.help}>
47
+ {Astro.slots.has('description') ? <slot name="description" /> : description}
48
+ </p>
49
+ )
50
+ }
51
+ {
52
+ hasError && (
53
+ <p class="x-field__error" id={ids.error}>
54
+ {Astro.slots.has('error') ? <slot name="error" /> : error}
55
+ </p>
56
+ )
57
+ }
58
+ </fieldset>
@@ -0,0 +1,17 @@
1
+ ---
2
+ import './field.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { choiceClass } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'input'>, 'class' | 'type' | 'role'> & {
7
+ class?: string
8
+ }
9
+
10
+ const { class: className, ...rest } = Astro.props
11
+ const input = { ...rest, type: 'radio' as const, class: 'x-radio__input' }
12
+ ---
13
+
14
+ <label class={choiceClass('radio', { className })}>
15
+ <input {...input} />
16
+ <span class="x-radio__label"><slot /></span>
17
+ </label>
@@ -0,0 +1,13 @@
1
+ ---
2
+ import './field.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { selectClass } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'select'>, 'class'> & {
7
+ class?: string
8
+ }
9
+
10
+ const { class: className, ...rest } = Astro.props
11
+ ---
12
+
13
+ <select {...{ ...rest, class: selectClass({ className }) }}><slot /></select>
@@ -0,0 +1,17 @@
1
+ ---
2
+ import './field.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { choiceClass } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'input'>, 'class' | 'type' | 'role'> & {
7
+ class?: string
8
+ }
9
+
10
+ const { class: className, ...rest } = Astro.props
11
+ const input = { ...rest, type: 'checkbox' as const, role: 'switch' as const, class: 'x-switch__input' }
12
+ ---
13
+
14
+ <label class={choiceClass('switch', { className })}>
15
+ <input {...input} />
16
+ <span class="x-switch__label"><slot /></span>
17
+ </label>