@chiralkit/design-system 0.1.0-alpha.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 (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/dist/chiralkit-ds/chiralkit-ds.css +1 -0
  4. package/dist/chiralkit-ds/chiralkit-ds.esm.js +1 -0
  5. package/dist/chiralkit-ds/index.esm.js +1 -0
  6. package/dist/chiralkit-ds/p-038fa008.entry.js +1 -0
  7. package/dist/chiralkit-ds/p-2fdc1012.entry.js +1 -0
  8. package/dist/chiralkit-ds/p-306ec33b.entry.js +1 -0
  9. package/dist/chiralkit-ds/p-3f704698.entry.js +1 -0
  10. package/dist/chiralkit-ds/p-41081d22.entry.js +1 -0
  11. package/dist/chiralkit-ds/p-7d7e5aac.entry.js +1 -0
  12. package/dist/chiralkit-ds/p-84b801f8.entry.js +1 -0
  13. package/dist/chiralkit-ds/p-a28c71b4.entry.js +1 -0
  14. package/dist/chiralkit-ds/p-be84312f.entry.js +1 -0
  15. package/dist/chiralkit-ds/p-c8015fe6.entry.js +1 -0
  16. package/dist/chiralkit-ds/p-d36a6bea.entry.js +1 -0
  17. package/dist/chiralkit-ds/p-e-glNjK6.js +2 -0
  18. package/dist/chiralkit-ds/p-f0f9ff61.entry.js +1 -0
  19. package/dist/cjs/chiralkit-ds.cjs.js +24 -0
  20. package/dist/cjs/ds-badge.cjs.entry.js +18 -0
  21. package/dist/cjs/ds-button.cjs.entry.js +26 -0
  22. package/dist/cjs/ds-card.cjs.entry.js +17 -0
  23. package/dist/cjs/ds-checkbox.cjs.entry.js +67 -0
  24. package/dist/cjs/ds-code-block.cjs.entry.js +39 -0
  25. package/dist/cjs/ds-dialog.cjs.entry.js +67 -0
  26. package/dist/cjs/ds-input.cjs.entry.js +50 -0
  27. package/dist/cjs/ds-loader.cjs.entry.js +26 -0
  28. package/dist/cjs/ds-select.cjs.entry.js +85 -0
  29. package/dist/cjs/ds-tabs.cjs.entry.js +138 -0
  30. package/dist/cjs/ds-textarea.cjs.entry.js +50 -0
  31. package/dist/cjs/ds-tooltip.cjs.entry.js +233 -0
  32. package/dist/cjs/index-mMvyq7U_.js +2018 -0
  33. package/dist/cjs/index.cjs.js +10 -0
  34. package/dist/cjs/loader.cjs.js +12 -0
  35. package/dist/collection/collection-manifest.json +24 -0
  36. package/dist/collection/components/ds-badge/ds-badge.css +36 -0
  37. package/dist/collection/components/ds-badge/ds-badge.js +93 -0
  38. package/dist/collection/components/ds-button/ds-button.css +66 -0
  39. package/dist/collection/components/ds-button/ds-button.js +197 -0
  40. package/dist/collection/components/ds-card/ds-card.css +12 -0
  41. package/dist/collection/components/ds-card/ds-card.js +68 -0
  42. package/dist/collection/components/ds-checkbox/ds-checkbox.css +22 -0
  43. package/dist/collection/components/ds-checkbox/ds-checkbox.js +339 -0
  44. package/dist/collection/components/ds-code-block/ds-code-block.css +65 -0
  45. package/dist/collection/components/ds-code-block/ds-code-block.js +162 -0
  46. package/dist/collection/components/ds-dialog/ds-dialog.css +30 -0
  47. package/dist/collection/components/ds-dialog/ds-dialog.js +313 -0
  48. package/dist/collection/components/ds-input/ds-input.css +38 -0
  49. package/dist/collection/components/ds-input/ds-input.js +353 -0
  50. package/dist/collection/components/ds-loader/ds-loader.css +94 -0
  51. package/dist/collection/components/ds-loader/ds-loader.js +149 -0
  52. package/dist/collection/components/ds-select/ds-select.css +69 -0
  53. package/dist/collection/components/ds-select/ds-select.js +235 -0
  54. package/dist/collection/components/ds-tabs/ds-tabs.css +59 -0
  55. package/dist/collection/components/ds-tabs/ds-tabs.js +248 -0
  56. package/dist/collection/components/ds-textarea/ds-textarea.css +41 -0
  57. package/dist/collection/components/ds-textarea/ds-textarea.js +342 -0
  58. package/dist/collection/components/ds-tooltip/ds-tooltip.css +31 -0
  59. package/dist/collection/components/ds-tooltip/ds-tooltip.js +318 -0
  60. package/dist/collection/components.js +2 -0
  61. package/dist/collection/index.js +1 -0
  62. package/dist/components/ds-badge.d.ts +11 -0
  63. package/dist/components/ds-badge.js +1 -0
  64. package/dist/components/ds-button.d.ts +11 -0
  65. package/dist/components/ds-button.js +1 -0
  66. package/dist/components/ds-card.d.ts +11 -0
  67. package/dist/components/ds-card.js +1 -0
  68. package/dist/components/ds-checkbox.d.ts +11 -0
  69. package/dist/components/ds-checkbox.js +1 -0
  70. package/dist/components/ds-code-block.d.ts +11 -0
  71. package/dist/components/ds-code-block.js +1 -0
  72. package/dist/components/ds-dialog.d.ts +11 -0
  73. package/dist/components/ds-dialog.js +1 -0
  74. package/dist/components/ds-input.d.ts +11 -0
  75. package/dist/components/ds-input.js +1 -0
  76. package/dist/components/ds-loader.d.ts +11 -0
  77. package/dist/components/ds-loader.js +1 -0
  78. package/dist/components/ds-select.d.ts +11 -0
  79. package/dist/components/ds-select.js +1 -0
  80. package/dist/components/ds-tabs.d.ts +11 -0
  81. package/dist/components/ds-tabs.js +1 -0
  82. package/dist/components/ds-textarea.d.ts +11 -0
  83. package/dist/components/ds-textarea.js +1 -0
  84. package/dist/components/ds-tooltip.d.ts +11 -0
  85. package/dist/components/ds-tooltip.js +1 -0
  86. package/dist/components/index.d.ts +35 -0
  87. package/dist/components/index.js +1 -0
  88. package/dist/components/p-DDUZow8F.js +1 -0
  89. package/dist/components/p-ZsYP0VBq.js +1 -0
  90. package/dist/components/p-k0ywxWpC.js +1 -0
  91. package/dist/esm/chiralkit-ds.js +20 -0
  92. package/dist/esm/ds-badge.entry.js +16 -0
  93. package/dist/esm/ds-button.entry.js +24 -0
  94. package/dist/esm/ds-card.entry.js +15 -0
  95. package/dist/esm/ds-checkbox.entry.js +65 -0
  96. package/dist/esm/ds-code-block.entry.js +37 -0
  97. package/dist/esm/ds-dialog.entry.js +65 -0
  98. package/dist/esm/ds-input.entry.js +48 -0
  99. package/dist/esm/ds-loader.entry.js +24 -0
  100. package/dist/esm/ds-select.entry.js +83 -0
  101. package/dist/esm/ds-tabs.entry.js +136 -0
  102. package/dist/esm/ds-textarea.entry.js +48 -0
  103. package/dist/esm/ds-tooltip.entry.js +231 -0
  104. package/dist/esm/index-e-glNjK6.js +2008 -0
  105. package/dist/esm/index.js +3 -0
  106. package/dist/esm/loader.js +10 -0
  107. package/dist/index.cjs.js +1 -0
  108. package/dist/index.js +1 -0
  109. package/dist/layouts/app-shell.css +84 -0
  110. package/dist/tests/a11y.js +55 -0
  111. package/dist/types/Users/alex/projects/MyGitHub/chiralkit/packages/design-system/.stencil/tests/a11y.d.ts +17 -0
  112. package/dist/types/components/ds-badge/ds-badge.d.ts +54 -0
  113. package/dist/types/components/ds-button/ds-button.d.ts +75 -0
  114. package/dist/types/components/ds-card/ds-card.d.ts +53 -0
  115. package/dist/types/components/ds-checkbox/ds-checkbox.d.ts +94 -0
  116. package/dist/types/components/ds-code-block/ds-code-block.d.ts +83 -0
  117. package/dist/types/components/ds-dialog/ds-dialog.d.ts +148 -0
  118. package/dist/types/components/ds-input/ds-input.d.ts +104 -0
  119. package/dist/types/components/ds-loader/ds-loader.d.ts +64 -0
  120. package/dist/types/components/ds-select/ds-select.d.ts +123 -0
  121. package/dist/types/components/ds-tabs/ds-tabs.d.ts +155 -0
  122. package/dist/types/components/ds-textarea/ds-textarea.d.ts +93 -0
  123. package/dist/types/components/ds-tooltip/ds-tooltip.d.ts +26 -0
  124. package/dist/types/components.d.ts +3068 -0
  125. package/dist/types/index.d.ts +1 -0
  126. package/dist/types/stencil-public-runtime.d.ts +1861 -0
  127. package/docs.json +2112 -0
  128. package/loader/cdn.js +1 -0
  129. package/loader/index.cjs.js +1 -0
  130. package/loader/index.d.ts +24 -0
  131. package/loader/index.es2017.js +1 -0
  132. package/loader/index.js +2 -0
  133. package/package.json +78 -0
  134. package/react/components/ds-badge.tsx +25 -0
  135. package/react/components/ds-button.tsx +25 -0
  136. package/react/components/ds-card.tsx +25 -0
  137. package/react/components/ds-checkbox.tsx +25 -0
  138. package/react/components/ds-code-block.tsx +25 -0
  139. package/react/components/ds-dialog.tsx +31 -0
  140. package/react/components/ds-input.tsx +25 -0
  141. package/react/components/ds-loader.tsx +25 -0
  142. package/react/components/ds-select.tsx +25 -0
  143. package/react/components/ds-tabs.tsx +25 -0
  144. package/react/components/ds-textarea.tsx +25 -0
  145. package/react/components/ds-tooltip.tsx +25 -0
  146. package/react/components.ts +162 -0
@@ -0,0 +1,353 @@
1
+ import { h, Host, } from "@stencil/core";
2
+ /**
3
+ * ## Accessibility
4
+ *
5
+ * **Category:** form-field
6
+ * **APG pattern:** N/A — native element. This wraps a native `<input>`;
7
+ * there is no WAI-ARIA APG pattern for a plain text input because the
8
+ * browser already implements it natively. Reference:
9
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
10
+ *
11
+ * **Keyboard interactions:**
12
+ * - Tab: moves focus to the native `<input>` (shadow DOM does not
13
+ * interrupt tab order).
14
+ * - Printable characters: insert at the caret position (native).
15
+ * - Backspace / Delete: native editing.
16
+ * - Arrow keys: move the caret within the value (native); for
17
+ * `type="number"` Up/Down step the numeric value (native).
18
+ * - Enter: on `type="search"` may submit the enclosing form (native);
19
+ * no custom handling.
20
+ * - Escape: no native meaning for a text input.
21
+ *
22
+ * **ARIA:**
23
+ * - Roles used: none explicit — the native `<input>` supplies its
24
+ * implicit role (`textbox`, `searchbox`, etc. depending on `type`).
25
+ * - State attributes: `disabled` (native attribute, not `aria-disabled`),
26
+ * `aria-invalid` (from the `invalid` prop), `aria-required` (from the
27
+ * `required` prop), `aria-describedby` (from the `describedBy` prop,
28
+ * expected to reference a light-DOM node id — typically a hint or
29
+ * error message rendered by the consumer alongside the field).
30
+ * - Labeling strategy: `aria-label` set from the `label` prop onto the
31
+ * internal `<input>`. This is the **canonical, recommended** mode —
32
+ * explicit and independent of DOM structure. Consumers MUST pass
33
+ * `label` (or otherwise give the field an accessible name) — an
34
+ * `<input>` with no accessible name is a WCAG failure (axe rule
35
+ * `label`). Wrapping the host in a light-DOM `<label>` (e.g.
36
+ * `<label>Email <ds-input></ds-input></label>`) also produces an
37
+ * accessible name in Chromium via flattened-tree resolution — the
38
+ * same secondary mode documented on `ds-select`, verified in this
39
+ * component's e2e tests. Setting `aria-label` directly on the
40
+ * `<ds-input>` host does NOT work: shadow-DOM attribute forwarding
41
+ * would be required — use the `label` prop instead.
42
+ *
43
+ * **Focus:**
44
+ * - On mount: nothing auto-focuses.
45
+ * - On value change: focus remains on the native `<input>`.
46
+ * - Focus indicator: the browser default `:focus-visible` ring is kept
47
+ * on purpose — a themed focus ring using `--ds-color-focus` is a
48
+ * future decision once the DS ships a shared focus utility. Not
49
+ * overridden by `ds-input.css`.
50
+ *
51
+ * **Motion:**
52
+ * - Animations: none.
53
+ * - Reduced-motion behavior: N/A — no animation to collapse.
54
+ *
55
+ * **Contrast:**
56
+ * - Text: inherits `color: inherit` / `currentColor`; ratio depends on
57
+ * the consumer's theme, not verified in isolation here.
58
+ * - Border: `color-mix(in oklch, currentColor 22%, transparent)`,
59
+ * matching `ds-select` — inherits whatever contrast the host page
60
+ * sets. WCAG 1.4.11 (3:1 non-text) is the consumer's responsibility
61
+ * in themes that override the surface.
62
+ *
63
+ * **Screen reader announcement:**
64
+ * - Rendered state: announces the `label` prop text as the accessible
65
+ * name, followed by the browser/AT's role announcement for the
66
+ * input type ("edit text", "search text", "password", "phone
67
+ * number", "email"…). When `required=true` the announcement
68
+ * includes "required"; when `invalid=true` it includes "invalid".
69
+ * - State change: on value edit, AT typically echoes the typed
70
+ * character (native input behavior); no `aria-live` added, per the
71
+ * spec's guidance against live-region overuse.
72
+ *
73
+ * **Consumer contract:**
74
+ * - `placeholder` is a hint, not a label. It disappears on focus in
75
+ * most browsers and is not reliably announced by every AT — always
76
+ * pair with `label`.
77
+ * - For `type="password"` in a real form, pair with a light-DOM
78
+ * `<input type="hidden">` or a form-associated ancestor that gives
79
+ * the browser's password manager an `autocomplete` hint. This
80
+ * component intentionally does not expose `autocomplete` in v1;
81
+ * consumers that need it can render a native `<input>` for that
82
+ * field until the prop is added.
83
+ * - When `invalid=true`, pair with a `describedBy` id that points at
84
+ * a visible error message; `aria-invalid` alone gives the AT the
85
+ * status but not the reason.
86
+ */
87
+ export class DsInput {
88
+ inputEl;
89
+ value;
90
+ name;
91
+ type = "text";
92
+ placeholder;
93
+ disabled = false;
94
+ required = false;
95
+ invalid = false;
96
+ describedBy;
97
+ label;
98
+ size = "md";
99
+ dsInput;
100
+ watchValue(newValue) {
101
+ if (this.inputEl && this.inputEl.value !== (newValue ?? "")) {
102
+ this.inputEl.value = newValue ?? "";
103
+ }
104
+ }
105
+ componentDidLoad() {
106
+ if (this.inputEl && this.value !== undefined) {
107
+ this.inputEl.value = this.value;
108
+ }
109
+ }
110
+ onInput = (event) => {
111
+ const value = event.target.value;
112
+ this.dsInput.emit(value);
113
+ };
114
+ render() {
115
+ const cls = `ds-input ds-input--${this.size}`;
116
+ return (h(Host, { key: 'efce39c6ecfb8dda71577e7fb7b285ddc16287f5' }, h("input", { key: '5f6ad573f20e6eca20f42aa96c8acdf386508d1c', class: cls, type: this.type, name: this.name, placeholder: this.placeholder, disabled: this.disabled, "aria-label": this.label, "aria-required": this.required ? "true" : undefined, "aria-invalid": this.invalid ? "true" : undefined, "aria-describedby": this.describedBy, ref: (el) => (this.inputEl = el), onInput: this.onInput })));
117
+ }
118
+ static get is() { return "ds-input"; }
119
+ static get encapsulation() { return "shadow"; }
120
+ static get originalStyleUrls() {
121
+ return {
122
+ "$": ["ds-input.css"]
123
+ };
124
+ }
125
+ static get styleUrls() {
126
+ return {
127
+ "$": ["ds-input.css"]
128
+ };
129
+ }
130
+ static get properties() {
131
+ return {
132
+ "value": {
133
+ "type": "string",
134
+ "mutable": false,
135
+ "complexType": {
136
+ "original": "string",
137
+ "resolved": "string | undefined",
138
+ "references": {}
139
+ },
140
+ "required": false,
141
+ "optional": true,
142
+ "docs": {
143
+ "tags": [],
144
+ "text": ""
145
+ },
146
+ "getter": false,
147
+ "setter": false,
148
+ "reflect": false,
149
+ "attribute": "value"
150
+ },
151
+ "name": {
152
+ "type": "string",
153
+ "mutable": false,
154
+ "complexType": {
155
+ "original": "string",
156
+ "resolved": "string | undefined",
157
+ "references": {}
158
+ },
159
+ "required": false,
160
+ "optional": true,
161
+ "docs": {
162
+ "tags": [],
163
+ "text": ""
164
+ },
165
+ "getter": false,
166
+ "setter": false,
167
+ "reflect": false,
168
+ "attribute": "name"
169
+ },
170
+ "type": {
171
+ "type": "string",
172
+ "mutable": false,
173
+ "complexType": {
174
+ "original": "| \"text\"\n | \"email\"\n | \"password\"\n | \"search\"\n | \"tel\"\n | \"url\"\n | \"number\"",
175
+ "resolved": "\"email\" | \"number\" | \"password\" | \"search\" | \"tel\" | \"text\" | \"url\"",
176
+ "references": {}
177
+ },
178
+ "required": false,
179
+ "optional": false,
180
+ "docs": {
181
+ "tags": [],
182
+ "text": ""
183
+ },
184
+ "getter": false,
185
+ "setter": false,
186
+ "reflect": false,
187
+ "attribute": "type",
188
+ "defaultValue": "\"text\""
189
+ },
190
+ "placeholder": {
191
+ "type": "string",
192
+ "mutable": false,
193
+ "complexType": {
194
+ "original": "string",
195
+ "resolved": "string | undefined",
196
+ "references": {}
197
+ },
198
+ "required": false,
199
+ "optional": true,
200
+ "docs": {
201
+ "tags": [],
202
+ "text": ""
203
+ },
204
+ "getter": false,
205
+ "setter": false,
206
+ "reflect": false,
207
+ "attribute": "placeholder"
208
+ },
209
+ "disabled": {
210
+ "type": "boolean",
211
+ "mutable": false,
212
+ "complexType": {
213
+ "original": "boolean",
214
+ "resolved": "boolean",
215
+ "references": {}
216
+ },
217
+ "required": false,
218
+ "optional": false,
219
+ "docs": {
220
+ "tags": [],
221
+ "text": ""
222
+ },
223
+ "getter": false,
224
+ "setter": false,
225
+ "reflect": false,
226
+ "attribute": "disabled",
227
+ "defaultValue": "false"
228
+ },
229
+ "required": {
230
+ "type": "boolean",
231
+ "mutable": false,
232
+ "complexType": {
233
+ "original": "boolean",
234
+ "resolved": "boolean",
235
+ "references": {}
236
+ },
237
+ "required": false,
238
+ "optional": false,
239
+ "docs": {
240
+ "tags": [],
241
+ "text": ""
242
+ },
243
+ "getter": false,
244
+ "setter": false,
245
+ "reflect": false,
246
+ "attribute": "required",
247
+ "defaultValue": "false"
248
+ },
249
+ "invalid": {
250
+ "type": "boolean",
251
+ "mutable": false,
252
+ "complexType": {
253
+ "original": "boolean",
254
+ "resolved": "boolean",
255
+ "references": {}
256
+ },
257
+ "required": false,
258
+ "optional": false,
259
+ "docs": {
260
+ "tags": [],
261
+ "text": ""
262
+ },
263
+ "getter": false,
264
+ "setter": false,
265
+ "reflect": false,
266
+ "attribute": "invalid",
267
+ "defaultValue": "false"
268
+ },
269
+ "describedBy": {
270
+ "type": "string",
271
+ "mutable": false,
272
+ "complexType": {
273
+ "original": "string",
274
+ "resolved": "string | undefined",
275
+ "references": {}
276
+ },
277
+ "required": false,
278
+ "optional": true,
279
+ "docs": {
280
+ "tags": [],
281
+ "text": ""
282
+ },
283
+ "getter": false,
284
+ "setter": false,
285
+ "reflect": false,
286
+ "attribute": "described-by"
287
+ },
288
+ "label": {
289
+ "type": "string",
290
+ "mutable": false,
291
+ "complexType": {
292
+ "original": "string",
293
+ "resolved": "string | undefined",
294
+ "references": {}
295
+ },
296
+ "required": false,
297
+ "optional": true,
298
+ "docs": {
299
+ "tags": [],
300
+ "text": ""
301
+ },
302
+ "getter": false,
303
+ "setter": false,
304
+ "reflect": false,
305
+ "attribute": "label"
306
+ },
307
+ "size": {
308
+ "type": "string",
309
+ "mutable": false,
310
+ "complexType": {
311
+ "original": "\"md\" | \"lg\"",
312
+ "resolved": "\"lg\" | \"md\"",
313
+ "references": {}
314
+ },
315
+ "required": false,
316
+ "optional": false,
317
+ "docs": {
318
+ "tags": [],
319
+ "text": ""
320
+ },
321
+ "getter": false,
322
+ "setter": false,
323
+ "reflect": false,
324
+ "attribute": "size",
325
+ "defaultValue": "\"md\""
326
+ }
327
+ };
328
+ }
329
+ static get events() {
330
+ return [{
331
+ "method": "dsInput",
332
+ "name": "dsInput",
333
+ "bubbles": true,
334
+ "cancelable": true,
335
+ "composed": true,
336
+ "docs": {
337
+ "tags": [],
338
+ "text": ""
339
+ },
340
+ "complexType": {
341
+ "original": "string",
342
+ "resolved": "string",
343
+ "references": {}
344
+ }
345
+ }];
346
+ }
347
+ static get watchers() {
348
+ return [{
349
+ "propName": "value",
350
+ "methodName": "watchValue"
351
+ }];
352
+ }
353
+ }
@@ -0,0 +1,94 @@
1
+ :host {
2
+ display: inline-flex;
3
+ color: currentColor;
4
+ line-height: 0;
5
+ }
6
+
7
+ .ds-loader {
8
+ display: block;
9
+ width: 40px;
10
+ height: 40px;
11
+ color: inherit;
12
+ }
13
+
14
+ .ds-loader--sm {
15
+ width: 16px;
16
+ height: 16px;
17
+ }
18
+
19
+ .ds-loader--md {
20
+ width: 40px;
21
+ height: 40px;
22
+ }
23
+
24
+ .ds-loader--lg {
25
+ width: 120px;
26
+ height: 120px;
27
+ }
28
+
29
+ .ds-loader__half {
30
+ transform-box: view-box;
31
+ transform-origin: 50% 50%;
32
+ animation: ds-loader-swing 1.4s cubic-bezier(0.6, 0, 0.4, 1) infinite;
33
+ }
34
+
35
+ .ds-loader__half--b {
36
+ animation-name: ds-loader-swing-mirror;
37
+ }
38
+
39
+ .ds-loader__mirror {
40
+ transform-box: view-box;
41
+ transform-origin: 50% 50%;
42
+ transform: scaleX(-1);
43
+ }
44
+
45
+ .ds-loader--speed-slow .ds-loader__half {
46
+ animation-duration: 2.2s;
47
+ }
48
+
49
+ .ds-loader--speed-fast .ds-loader__half {
50
+ animation-duration: 0.9s;
51
+ }
52
+
53
+ @keyframes ds-loader-swing {
54
+ 0% {
55
+ transform: rotate(180deg);
56
+ }
57
+ 50% {
58
+ transform: rotate(0deg);
59
+ }
60
+ 100% {
61
+ transform: rotate(180deg);
62
+ }
63
+ }
64
+
65
+ @keyframes ds-loader-swing-mirror {
66
+ 0% {
67
+ transform: rotate(-180deg);
68
+ }
69
+ 50% {
70
+ transform: rotate(0deg);
71
+ }
72
+ 100% {
73
+ transform: rotate(-180deg);
74
+ }
75
+ }
76
+
77
+ @media (prefers-reduced-motion: reduce) {
78
+ .ds-loader__half,
79
+ .ds-loader__half--b {
80
+ animation: ds-loader-pulse 2s ease-in-out infinite;
81
+ }
82
+
83
+ @keyframes ds-loader-pulse {
84
+ 0%,
85
+ 100% {
86
+ opacity: 0.4;
87
+ transform: rotate(0deg);
88
+ }
89
+ 50% {
90
+ opacity: 1;
91
+ transform: rotate(0deg);
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,149 @@
1
+ import { h, Host } from "@stencil/core";
2
+ /**
3
+ * ## Accessibility
4
+ *
5
+ * **Category:** static-display (indeterminate progress indicator)
6
+ * **APG pattern:** N/A — the loader is a passive presence indicator, not a
7
+ * `progressbar` (no measurable value). Uses `role="status"` per WAI-ARIA
8
+ * live-region semantics for indeterminate loading states.
9
+ *
10
+ * **Keyboard interactions:**
11
+ * - Tab: N/A — not focusable.
12
+ * - Enter: N/A.
13
+ * - Escape: N/A.
14
+ * - Arrow keys: N/A.
15
+ * - Printable characters: N/A.
16
+ *
17
+ * **ARIA:**
18
+ * - Roles used: `role="status"` on the host — an implicit polite live
19
+ * region ARIA lists as valid for "advisory information" including
20
+ * in-progress operations. The inner `<svg>` is decorative and carries
21
+ * `aria-hidden="true"` + `focusable="false"` so it never becomes a
22
+ * parallel target for screen readers.
23
+ * - State attributes: none. The loader has no state changes to announce
24
+ * beyond its own presence; the polite live region on mount is the
25
+ * announcement.
26
+ * - Labeling strategy: `aria-label` from the `label` prop (default
27
+ * "Loading"). Consumers pass a more specific string when the context
28
+ * warrants ("Loading ceremonies", "Signing in", …).
29
+ *
30
+ * **Focus:**
31
+ * - On mount: nothing auto-focuses.
32
+ * - On activate/open: N/A — not interactive.
33
+ * - On close/deactivate: N/A.
34
+ * - Focus indicator: N/A — the host is not focusable.
35
+ *
36
+ * **Motion:**
37
+ * - Animations: two mirrored halves of the logo path oscillate
38
+ * symmetrically around the visual center, meeting at the ring midpoint
39
+ * and separating again. Cycle 1.4s by default (`speed="normal"`);
40
+ * `speed="slow"` = 2.2s, `speed="fast"` = 0.9s.
41
+ * - Reduced-motion behavior: `@media (prefers-reduced-motion: reduce)`
42
+ * collapses both halves to a static rendering with a slow opacity
43
+ * pulse (2s ease-in-out). No rotation, no lateral movement.
44
+ *
45
+ * **Contrast:**
46
+ * - Text: N/A — the visible surface is the SVG stroke, not text.
47
+ * - Non-text: stroke inherits `currentColor` from the host. Consumers are
48
+ * responsible for confirming the stroke against its background meets
49
+ * the ≥ 3:1 non-text contrast threshold (WCAG 1.4.11) where the loader
50
+ * conveys state (e.g. distinguishing "still loading" from "idle").
51
+ *
52
+ * **Screen reader announcement:**
53
+ * - Rendered state: the polite live region announces the label
54
+ * (e.g. "Loading") when the loader mounts.
55
+ * - State change: N/A — the loader itself has no state that changes.
56
+ * Consumers unmount the loader when the awaited work completes; that
57
+ * removal is silent, which is the intended behavior (the arriving
58
+ * content is the announcement).
59
+ */
60
+ export class DsLoader {
61
+ size = "md";
62
+ label = "Loading";
63
+ speed = "normal";
64
+ render() {
65
+ const cls = [
66
+ "ds-loader",
67
+ `ds-loader--${this.size}`,
68
+ `ds-loader--speed-${this.speed}`,
69
+ ].join(" ");
70
+ return (h(Host, { key: 'd7017e7e3c970e27bffa30c193abd937c5ec13cd', role: "status", "aria-label": this.label }, h("svg", { key: '9eb10695249bc4c833b6347d38226fdc37d4be7d', class: cls, viewBox: "-48 -48 96 96", "aria-hidden": "true", focusable: "false" }, h("g", { key: '711ee60648593d216687087c5b2145ec25120bc5', class: "ds-loader__half ds-loader__half--a" }, h("path", { key: '9efdc31104b6037a57d7bdc802303938f7285666', d: LOGO_PATH_D, fill: "none", stroke: "currentColor", "stroke-width": "14", "stroke-linecap": "round", "stroke-linejoin": "round" })), h("g", { key: 'fcbeefc154e0e9db5b0378f77dafa3f9c0162e92', class: "ds-loader__half ds-loader__half--b" }, h("g", { key: '790b3ec1c4b262352cc383ab6b9f7d5cbb77474b', class: "ds-loader__mirror" }, h("path", { key: '694713be37532568852b2de2530cc50c1ff3b8de', d: LOGO_PATH_D, fill: "none", stroke: "currentColor", "stroke-width": "14", "stroke-linecap": "round", "stroke-linejoin": "round" }))))));
71
+ }
72
+ static get is() { return "ds-loader"; }
73
+ static get encapsulation() { return "shadow"; }
74
+ static get originalStyleUrls() {
75
+ return {
76
+ "$": ["ds-loader.css"]
77
+ };
78
+ }
79
+ static get styleUrls() {
80
+ return {
81
+ "$": ["ds-loader.css"]
82
+ };
83
+ }
84
+ static get properties() {
85
+ return {
86
+ "size": {
87
+ "type": "string",
88
+ "mutable": false,
89
+ "complexType": {
90
+ "original": "\"sm\" | \"md\" | \"lg\"",
91
+ "resolved": "\"lg\" | \"md\" | \"sm\"",
92
+ "references": {}
93
+ },
94
+ "required": false,
95
+ "optional": false,
96
+ "docs": {
97
+ "tags": [],
98
+ "text": ""
99
+ },
100
+ "getter": false,
101
+ "setter": false,
102
+ "reflect": false,
103
+ "attribute": "size",
104
+ "defaultValue": "\"md\""
105
+ },
106
+ "label": {
107
+ "type": "string",
108
+ "mutable": false,
109
+ "complexType": {
110
+ "original": "string",
111
+ "resolved": "string",
112
+ "references": {}
113
+ },
114
+ "required": false,
115
+ "optional": false,
116
+ "docs": {
117
+ "tags": [],
118
+ "text": ""
119
+ },
120
+ "getter": false,
121
+ "setter": false,
122
+ "reflect": false,
123
+ "attribute": "label",
124
+ "defaultValue": "\"Loading\""
125
+ },
126
+ "speed": {
127
+ "type": "string",
128
+ "mutable": false,
129
+ "complexType": {
130
+ "original": "\"slow\" | \"normal\" | \"fast\"",
131
+ "resolved": "\"fast\" | \"normal\" | \"slow\"",
132
+ "references": {}
133
+ },
134
+ "required": false,
135
+ "optional": false,
136
+ "docs": {
137
+ "tags": [],
138
+ "text": ""
139
+ },
140
+ "getter": false,
141
+ "setter": false,
142
+ "reflect": false,
143
+ "attribute": "speed",
144
+ "defaultValue": "\"normal\""
145
+ }
146
+ };
147
+ }
148
+ }
149
+ const LOGO_PATH_D = "M14.09 -27.22 L13.04 -28.53 L11.94 -29.76 L10.78 -30.92 L9.57 -32.01 L8.30 -33.02 L7.00 -33.95 L5.65 -34.80 L4.27 -35.57 L2.85 -36.25 L1.40 -36.84 L-0.07 -37.35 L-1.56 -37.76 L-3.07 -38.08 L-4.59 -38.31 L-6.13 -38.45 L-7.66 -38.50 L-9.20 -38.45 L-10.73 -38.31 L-12.25 -38.08 L-13.76 -37.76 L-15.25 -37.35 L-16.73 -36.84 L-18.17 -36.25 L-19.59 -35.57 L-20.97 -34.80 L-22.32 -33.95 L-23.63 -33.02 L-24.89 -32.01 L-26.10 -30.92 L-27.26 -29.76 L-28.37 -28.53 L-29.41 -27.22 L-30.40 -25.86 L-31.32 -24.42 L-32.17 -22.93 L-32.95 -21.39 L-33.66 -19.79 L-34.30 -18.15 L-34.86 -16.46 L-35.34 -14.73 L-35.74 -12.97 L-36.06 -11.18 L-36.29 -9.35 L-36.44 -7.51 L-36.50 -5.65 L-36.48 -3.77 L-36.37 -1.89 L-36.18 0 L-35.90 1.89 L-35.53 3.77 L-35.07 5.65 L-34.53 7.51 L-33.90 9.35 L-33.18 11.18 L-32.39 12.97 L-31.51 14.73 L-30.55 16.46 L-29.51 18.15 L-28.40 19.79 L-27.21 21.39 L-25.95 22.93 L-24.61 24.42 L-23.22 25.86 L-21.75 27.22 L-20.23 28.53 L-18.64 29.76 L-17.00 30.92 L-15.31 32.01 L-13.57 33.02 L-11.79 33.95 L-9.96 34.80 L-8.10 35.57 L-6.20 36.25 L-4.28 36.84 L-2.33 37.35 L-0.35 37.76 L1.63 38.08 L3.64 38.31 L5.65 38.45 L7.66 38.50 L9.68 38.45 L11.69 38.31 L13.69 38.08 L15.68 37.76 L17.65 37.35 L19.60 36.84 L21.53 36.25 L23.42 35.57 L25.28 34.80 L27.11 33.95 L28.89 33.02 L30.63 32.01 L32.32 30.92 L33.96 29.76 L35.55 28.53 L37.07 27.22";
@@ -0,0 +1,69 @@
1
+ :host {
2
+ display: inline-block;
3
+ position: relative;
4
+ }
5
+
6
+ .ds-select {
7
+ font-family: var(--ds-font-family);
8
+ font-size: var(--ds-font-size-md);
9
+ background-color: transparent;
10
+ color: inherit;
11
+ border: 1px solid color-mix(in oklch, currentColor 22%, transparent);
12
+ cursor: pointer;
13
+ -webkit-appearance: none;
14
+ appearance: none;
15
+ }
16
+
17
+ /*
18
+ * The caret is drawn on a ::after pseudo-element of :host rather than as a
19
+ * background-image/mask-image on .ds-select itself. mask-image masks the
20
+ * ENTIRE painted box of the element it's applied to (text, border, and all)
21
+ * -- it is not layered behind content the way background-image is. Applying
22
+ * it directly to the <select> would clip away the option text and border,
23
+ * leaving only the tiny caret shape visible. Confirmed empirically before
24
+ * writing this. Using a dedicated pseudo-element keeps the mask contained to
25
+ * just the caret glyph while background-color: currentColor makes it track
26
+ * the host's inherited color (fixing the original bug where the SVG's own
27
+ * currentColor resolved to black inside the isolated background-image
28
+ * context).
29
+ */
30
+ :host::after {
31
+ content: "";
32
+ position: absolute;
33
+ top: 50%;
34
+ right: var(--ds-spacing-sm);
35
+ width: 12px;
36
+ height: 12px;
37
+ transform: translateY(-50%);
38
+ background-color: currentColor;
39
+ -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'><path d='M2.5 4.5L6 8L9.5 4.5' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
40
+ mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'><path d='M2.5 4.5L6 8L9.5 4.5' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
41
+ -webkit-mask-repeat: no-repeat;
42
+ mask-repeat: no-repeat;
43
+ -webkit-mask-position: center;
44
+ mask-position: center;
45
+ -webkit-mask-size: contain;
46
+ mask-size: contain;
47
+ pointer-events: none;
48
+ }
49
+
50
+ .ds-select:disabled {
51
+ opacity: 0.5;
52
+ cursor: not-allowed;
53
+ }
54
+
55
+ :host(:has(.ds-select:disabled))::after {
56
+ opacity: 0.5;
57
+ }
58
+
59
+ .ds-select--md {
60
+ border-radius: var(--ds-radius-sm);
61
+ padding: var(--ds-spacing-sm) var(--ds-spacing-lg);
62
+ padding-right: calc(var(--ds-spacing-lg) + var(--ds-spacing-lg));
63
+ }
64
+
65
+ .ds-select--lg {
66
+ border-radius: var(--ds-radius-md);
67
+ padding: var(--ds-spacing-md) var(--ds-spacing-xl);
68
+ padding-right: calc(var(--ds-spacing-xl) + var(--ds-spacing-lg));
69
+ }