@livenetworks/ashlar 1.3.2

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 (232) hide show
  1. package/README.md +177 -0
  2. package/js/COMPONENTS.md +1102 -0
  3. package/js/index.js +41 -0
  4. package/js/ln-accordion/README.md +137 -0
  5. package/js/ln-accordion/ln-accordion.js +1 -0
  6. package/js/ln-accordion/src/ln-accordion.js +41 -0
  7. package/js/ln-ajax/README.md +91 -0
  8. package/js/ln-ajax/ln-ajax.js +1 -0
  9. package/js/ln-ajax/src/ln-ajax.js +277 -0
  10. package/js/ln-api-connector/README.md +150 -0
  11. package/js/ln-api-connector/ln-api-connector.js +1 -0
  12. package/js/ln-api-connector/src/ln-api-connector.js +265 -0
  13. package/js/ln-autoresize/README.md +80 -0
  14. package/js/ln-autoresize/ln-autoresize.js +1 -0
  15. package/js/ln-autoresize/src/ln-autoresize.js +47 -0
  16. package/js/ln-autosave/README.md +92 -0
  17. package/js/ln-autosave/ln-autosave.js +1 -0
  18. package/js/ln-autosave/src/ln-autosave.js +147 -0
  19. package/js/ln-circular-progress/README.md +161 -0
  20. package/js/ln-circular-progress/ln-circular-progress.js +1 -0
  21. package/js/ln-circular-progress/src/ln-circular-progress.js +133 -0
  22. package/js/ln-confirm/README.md +86 -0
  23. package/js/ln-confirm/_ln-confirm.scss +13 -0
  24. package/js/ln-confirm/ln-confirm.js +1 -0
  25. package/js/ln-confirm/src/ln-confirm.js +131 -0
  26. package/js/ln-core/crypto.js +83 -0
  27. package/js/ln-core/helpers.js +411 -0
  28. package/js/ln-core/index.js +5 -0
  29. package/js/ln-core/persist.js +71 -0
  30. package/js/ln-core/positioning.js +207 -0
  31. package/js/ln-core/reactive.js +74 -0
  32. package/js/ln-couchdb-connector/README.md +156 -0
  33. package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
  34. package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +348 -0
  35. package/js/ln-data-coordinator/README.md +165 -0
  36. package/js/ln-data-coordinator/ln-data-coordinator.js +1 -0
  37. package/js/ln-data-coordinator/src/ln-data-coordinator.js +249 -0
  38. package/js/ln-data-store/README.md +94 -0
  39. package/js/ln-data-store/ln-data-store.js +1 -0
  40. package/js/ln-data-store/src/ln-data-store.js +699 -0
  41. package/js/ln-data-table/README.md +110 -0
  42. package/js/ln-data-table/ln-data-table.js +1 -0
  43. package/js/ln-data-table/ln-data-table.scss +10 -0
  44. package/js/ln-data-table/src/ln-data-table.js +1103 -0
  45. package/js/ln-date/README.md +151 -0
  46. package/js/ln-date/ln-date.js +1 -0
  47. package/js/ln-date/src/ln-date.js +442 -0
  48. package/js/ln-dropdown/README.md +117 -0
  49. package/js/ln-dropdown/ln-dropdown.js +1 -0
  50. package/js/ln-dropdown/ln-dropdown.scss +15 -0
  51. package/js/ln-dropdown/src/ln-dropdown.js +174 -0
  52. package/js/ln-external-links/README.md +341 -0
  53. package/js/ln-external-links/ln-external-links.js +1 -0
  54. package/js/ln-external-links/src/ln-external-links.js +116 -0
  55. package/js/ln-filter/README.md +99 -0
  56. package/js/ln-filter/ln-filter.js +1 -0
  57. package/js/ln-filter/ln-filter.scss +7 -0
  58. package/js/ln-filter/src/ln-filter.js +404 -0
  59. package/js/ln-form/README.md +101 -0
  60. package/js/ln-form/ln-form.js +1 -0
  61. package/js/ln-form/src/ln-form.js +199 -0
  62. package/js/ln-http/README.md +89 -0
  63. package/js/ln-http/ln-http.js +1 -0
  64. package/js/ln-http/src/ln-http.js +219 -0
  65. package/js/ln-icons/README.md +88 -0
  66. package/js/ln-icons/ln-icons.js +1 -0
  67. package/js/ln-icons/src/ln-icons.js +169 -0
  68. package/js/ln-link/README.md +303 -0
  69. package/js/ln-link/ln-link.js +1 -0
  70. package/js/ln-link/src/ln-link.js +196 -0
  71. package/js/ln-modal/README.md +154 -0
  72. package/js/ln-modal/ln-modal.js +1 -0
  73. package/js/ln-modal/ln-modal.scss +11 -0
  74. package/js/ln-modal/src/ln-modal.js +201 -0
  75. package/js/ln-nav/README.md +70 -0
  76. package/js/ln-nav/ln-nav.js +1 -0
  77. package/js/ln-nav/src/ln-nav.js +177 -0
  78. package/js/ln-number/README.md +122 -0
  79. package/js/ln-number/ln-number.js +1 -0
  80. package/js/ln-number/src/ln-number.js +302 -0
  81. package/js/ln-popover/README.md +127 -0
  82. package/js/ln-popover/ln-popover.js +1 -0
  83. package/js/ln-popover/src/ln-popover.js +288 -0
  84. package/js/ln-progress/README.md +442 -0
  85. package/js/ln-progress/ln-progress.js +1 -0
  86. package/js/ln-progress/src/ln-progress.js +150 -0
  87. package/js/ln-search/README.md +83 -0
  88. package/js/ln-search/ln-search.js +1 -0
  89. package/js/ln-search/ln-search.scss +7 -0
  90. package/js/ln-search/src/ln-search.js +114 -0
  91. package/js/ln-sortable/README.md +95 -0
  92. package/js/ln-sortable/ln-sortable.js +1 -0
  93. package/js/ln-sortable/src/ln-sortable.js +203 -0
  94. package/js/ln-table/README.md +101 -0
  95. package/js/ln-table/ln-table-sort.js +1 -0
  96. package/js/ln-table/ln-table.js +1 -0
  97. package/js/ln-table/ln-table.scss +11 -0
  98. package/js/ln-table/src/ln-table-sort.js +168 -0
  99. package/js/ln-table/src/ln-table.js +473 -0
  100. package/js/ln-tabs/README.md +137 -0
  101. package/js/ln-tabs/ln-tabs.js +1 -0
  102. package/js/ln-tabs/src/ln-tabs.js +171 -0
  103. package/js/ln-time/README.md +81 -0
  104. package/js/ln-time/ln-time.js +1 -0
  105. package/js/ln-time/src/ln-time.js +192 -0
  106. package/js/ln-toast/README.md +122 -0
  107. package/js/ln-toast/ln-toast.js +15 -0
  108. package/js/ln-toast/src/ln-toast.js +210 -0
  109. package/js/ln-toast/template.html +14 -0
  110. package/js/ln-toggle/README.md +137 -0
  111. package/js/ln-toggle/ln-toggle.js +1 -0
  112. package/js/ln-toggle/src/ln-toggle.js +139 -0
  113. package/js/ln-tooltip/README.md +58 -0
  114. package/js/ln-tooltip/ln-tooltip.js +1 -0
  115. package/js/ln-tooltip/ln-tooltip.scss +9 -0
  116. package/js/ln-tooltip/src/ln-tooltip.js +169 -0
  117. package/js/ln-translations/README.md +96 -0
  118. package/js/ln-translations/ln-translations.js +1 -0
  119. package/js/ln-translations/src/ln-translations.js +275 -0
  120. package/js/ln-upload/README.md +180 -0
  121. package/js/ln-upload/ln-upload.js +1 -0
  122. package/js/ln-upload/ln-upload.scss +20 -0
  123. package/js/ln-upload/src/ln-upload.js +407 -0
  124. package/js/ln-validate/README.md +108 -0
  125. package/js/ln-validate/ln-validate.js +1 -0
  126. package/js/ln-validate/src/ln-validate.js +160 -0
  127. package/package.json +55 -0
  128. package/scss/base/_global.scss +83 -0
  129. package/scss/base/_reset.scss +17 -0
  130. package/scss/base/_typography.scss +125 -0
  131. package/scss/components/_accordion.scss +34 -0
  132. package/scss/components/_ajax.scss +15 -0
  133. package/scss/components/_alert.scss +5 -0
  134. package/scss/components/_app-shell.scss +15 -0
  135. package/scss/components/_avatar.scss +6 -0
  136. package/scss/components/_breadcrumbs.scss +33 -0
  137. package/scss/components/_button.scss +20 -0
  138. package/scss/components/_card.scss +10 -0
  139. package/scss/components/_chip.scss +5 -0
  140. package/scss/components/_circular-progress.scss +29 -0
  141. package/scss/components/_confirm.scss +5 -0
  142. package/scss/components/_data-table.scss +83 -0
  143. package/scss/components/_dropdown.scss +25 -0
  144. package/scss/components/_empty-state.scss +22 -0
  145. package/scss/components/_form.scss +100 -0
  146. package/scss/components/_layout.scss +8 -0
  147. package/scss/components/_link.scss +11 -0
  148. package/scss/components/_ln-table.scss +60 -0
  149. package/scss/components/_loader.scss +6 -0
  150. package/scss/components/_modal.scss +20 -0
  151. package/scss/components/_nav.scss +9 -0
  152. package/scss/components/_page-header.scss +10 -0
  153. package/scss/components/_popover.scss +10 -0
  154. package/scss/components/_progress.scss +17 -0
  155. package/scss/components/_prose.scss +5 -0
  156. package/scss/components/_scrollbar.scss +32 -0
  157. package/scss/components/_sections.scss +12 -0
  158. package/scss/components/_sidebar.scss +5 -0
  159. package/scss/components/_stat-card.scss +5 -0
  160. package/scss/components/_status-badge.scss +4 -0
  161. package/scss/components/_stepper.scss +5 -0
  162. package/scss/components/_table.scss +19 -0
  163. package/scss/components/_tabs.scss +21 -0
  164. package/scss/components/_timeline.scss +14 -0
  165. package/scss/components/_toast.scss +41 -0
  166. package/scss/components/_toggle.scss +81 -0
  167. package/scss/components/_tooltip.scss +18 -0
  168. package/scss/components/_translations.scss +111 -0
  169. package/scss/components/_upload.scss +51 -0
  170. package/scss/config/_breakpoints.scss +72 -0
  171. package/scss/config/_density.scss +117 -0
  172. package/scss/config/_icons.scss +37 -0
  173. package/scss/config/_mixins.scss +13 -0
  174. package/scss/config/_theme.scss +216 -0
  175. package/scss/config/_tokens.scss +419 -0
  176. package/scss/config/mixins/_accordion.scss +52 -0
  177. package/scss/config/mixins/_ajax.scss +39 -0
  178. package/scss/config/mixins/_alert.scss +82 -0
  179. package/scss/config/mixins/_app-shell.scss +312 -0
  180. package/scss/config/mixins/_avatar.scss +109 -0
  181. package/scss/config/mixins/_borders.scss +36 -0
  182. package/scss/config/mixins/_breadcrumbs.scss +72 -0
  183. package/scss/config/mixins/_breakpoints.scss +62 -0
  184. package/scss/config/mixins/_btn.scss +179 -0
  185. package/scss/config/mixins/_card.scss +338 -0
  186. package/scss/config/mixins/_chip.scss +66 -0
  187. package/scss/config/mixins/_circular-progress.scss +71 -0
  188. package/scss/config/mixins/_collapsible.scss +24 -0
  189. package/scss/config/mixins/_colors.scss +46 -0
  190. package/scss/config/mixins/_confirm.scss +31 -0
  191. package/scss/config/mixins/_data-table.scss +346 -0
  192. package/scss/config/mixins/_display.scss +32 -0
  193. package/scss/config/mixins/_dropdown.scss +143 -0
  194. package/scss/config/mixins/_empty-state.scss +30 -0
  195. package/scss/config/mixins/_focus.scss +55 -0
  196. package/scss/config/mixins/_footer.scss +42 -0
  197. package/scss/config/mixins/_form.scss +601 -0
  198. package/scss/config/mixins/_index.scss +58 -0
  199. package/scss/config/mixins/_interaction.scss +15 -0
  200. package/scss/config/mixins/_kbd.scss +22 -0
  201. package/scss/config/mixins/_layout.scss +117 -0
  202. package/scss/config/mixins/_link.scss +55 -0
  203. package/scss/config/mixins/_ln-table.scss +420 -0
  204. package/scss/config/mixins/_loader.scss +26 -0
  205. package/scss/config/mixins/_modal.scss +66 -0
  206. package/scss/config/mixins/_motion.scss +19 -0
  207. package/scss/config/mixins/_nav.scss +273 -0
  208. package/scss/config/mixins/_page-header.scss +69 -0
  209. package/scss/config/mixins/_popover.scss +25 -0
  210. package/scss/config/mixins/_position.scss +32 -0
  211. package/scss/config/mixins/_progress.scss +56 -0
  212. package/scss/config/mixins/_prose.scss +127 -0
  213. package/scss/config/mixins/_shadows.scss +8 -0
  214. package/scss/config/mixins/_sidebar.scss +95 -0
  215. package/scss/config/mixins/_sizing.scss +6 -0
  216. package/scss/config/mixins/_spacing.scss +19 -0
  217. package/scss/config/mixins/_stat-card.scss +68 -0
  218. package/scss/config/mixins/_status-badge.scss +83 -0
  219. package/scss/config/mixins/_stepper.scss +78 -0
  220. package/scss/config/mixins/_table.scss +215 -0
  221. package/scss/config/mixins/_tabs.scss +64 -0
  222. package/scss/config/mixins/_timeline.scss +69 -0
  223. package/scss/config/mixins/_toast.scss +148 -0
  224. package/scss/config/mixins/_tooltip.scss +111 -0
  225. package/scss/config/mixins/_transitions.scss +10 -0
  226. package/scss/config/mixins/_translations.scss +124 -0
  227. package/scss/config/mixins/_typography.scss +57 -0
  228. package/scss/config/mixins/_upload.scss +168 -0
  229. package/scss/ln-ashlar.scss +62 -0
  230. package/scss/tabler-icons.txt +5039 -0
  231. package/scss/utilities/_animations.scss +83 -0
  232. package/scss/utilities/_utilities.scss +49 -0
@@ -0,0 +1,601 @@
1
+ @use 'spacing' as *;
2
+ @use 'display' as *;
3
+ @use 'breakpoints' as *;
4
+ @use 'sizing' as *;
5
+ @use 'typography' as *;
6
+ @use 'colors' as *;
7
+ @use 'borders' as *;
8
+ @use 'transitions' as *;
9
+ @use 'motion' as *;
10
+ @use 'layout' as *;
11
+ @use 'interaction' as *;
12
+ @use 'position' as *;
13
+ @use 'focus' as *;
14
+ @use 'btn' as *;
15
+
16
+ // ─── Form layout ────────────────────────────────────────────────
17
+
18
+ @mixin form-label {
19
+ @include block;
20
+ font-size: var(--text-label-md);
21
+ line-height: var(--lh-label-md);
22
+ @include font-medium;
23
+ color: var(--color-fg);
24
+ // Structural label-to-input gap (not shell rhythm).
25
+ --margin-block: var(--size-xs);
26
+ margin-bottom: var(--margin-block);
27
+
28
+ // Required indicator — red * after label text
29
+ // Triggered by sibling input[required] via parent :has()
30
+ .form-element:has([required]) > & {
31
+ &::after {
32
+ content: ' *';
33
+ color: hsl(var(--color-error));
34
+ @include font-bold;
35
+ }
36
+ }
37
+ }
38
+
39
+ @mixin form-grid {
40
+ --gap: var(--size-md);
41
+ container-type: inline-size;
42
+ container-name: form-grid;
43
+ display: grid;
44
+ grid-template-columns: repeat(6, minmax(0, 1fr));
45
+ gap: var(--gap);
46
+
47
+ > * { margin: 0; }
48
+
49
+ @include cq-down(md, form-grid) {
50
+ grid-template-columns: repeat(1, minmax(0, 1fr));
51
+ }
52
+ }
53
+
54
+ @mixin form-actions {
55
+ --gap: var(--size-sm-up);
56
+ @include flex;
57
+ @include justify-end;
58
+ gap: var(--gap);
59
+ // Structural margin-top (not shell rhythm).
60
+ --margin-block: var(--size-lg);
61
+ margin-top: var(--margin-block);
62
+ // Structural padding-top (border-t offset). Kept literal to avoid
63
+ // cascading --padding-y into nested anchors, which use button-standard
64
+ // --padding-y from :root.
65
+ --padding-y: var(--size-md);
66
+ padding-top: var(--padding-y);
67
+ @include border-t;
68
+
69
+ a {
70
+ // Anchor-as-button: inherit neutral button chrome from the library,
71
+ // then reset the --gap rebind (form-actions parent sets a wider
72
+ // action-button gap; anchors want the tighter icon+text gap) and
73
+ // strip default anchor underline.
74
+ --gap: var(--size-sm);
75
+ @include button-base;
76
+ text-decoration: none;
77
+ }
78
+ }
79
+
80
+ // ─── Focus treatment (shared by form-input and icon-group) ─────
81
+
82
+ @mixin _form-focus-style {
83
+ border-color: var(--color-accent);
84
+ @include focus-background-shift;
85
+ }
86
+
87
+ // ─── Text inputs, textareas, selects ────────────────────────────
88
+
89
+ @mixin form-input {
90
+ @include w-full;
91
+ --padding-y: var(--size-sm);
92
+ --padding-x: var(--size-md);
93
+ padding: var(--padding-y) var(--padding-x);
94
+ font-size: var(--font-size);
95
+ line-height: var(--line-height);
96
+ color: var(--color-fg);
97
+ background: var(--color-bg);
98
+ @include border;
99
+ border-radius: var(--radius);
100
+ @include transition;
101
+ outline: none;
102
+
103
+ &:focus-visible {
104
+ @include _form-focus-style;
105
+ }
106
+
107
+ &::placeholder {
108
+ --color-fg: var(--fg-subtle);
109
+ color: var(--color-fg);
110
+ }
111
+
112
+ &:disabled {
113
+ @include opacity-50;
114
+ @include cursor-not-allowed;
115
+ --color-bg: var(--bg-sunken);
116
+ background: var(--color-bg);
117
+ }
118
+
119
+ // Hide native UA clear button on <input type="search"> — we never use it;
120
+ // it's visually inconsistent across browsers and fights our icon-group layout.
121
+ &[type="search"]::-webkit-search-cancel-button {
122
+ -webkit-appearance: none;
123
+ appearance: none;
124
+ }
125
+ }
126
+
127
+ @mixin form-textarea {
128
+ min-height: 6rem;
129
+ resize: vertical;
130
+ }
131
+
132
+ @mixin form-select {
133
+ appearance: none;
134
+ // Arrow icon — override --select-arrow to change per-theme
135
+ // (SVG data-URIs can't reference CSS custom properties, so the icon is static)
136
+ --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239ca3af' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
137
+ background-image: var(--select-arrow);
138
+ background-repeat: no-repeat;
139
+ // Intrinsic arrow offset (not shell padding rhythm).
140
+ background-position: right var(--size-sm-up) center;
141
+ padding-right: 2.5rem;
142
+ }
143
+
144
+ // ─── Input group — label wrapping icon(s) + input ──────────────────
145
+ //
146
+ // Pattern:
147
+ // <label>
148
+ // <svg class="ln-icon"/> ← leading icon (optional)
149
+ // <input type="…">
150
+ // <svg class="ln-icon"/> ← trailing icon (optional)
151
+ // </label>
152
+ //
153
+ // The <label> becomes the visual container (border, padding, focus
154
+ // ring). The nested <input> strips its form-input container chrome so
155
+ // that horizontal rhythm is owned by the label's px + gap, and vertical
156
+ // rhythm by the label's py. Without this, the input keeps its own
157
+ // padding and fights the wrapper — see the asymmetric spacing that
158
+ // motivated this mixin.
159
+ //
160
+ // Applied via semantic selector in components/_form.scss — no class
161
+ // needed on the <label>.
162
+
163
+ @mixin form-input-icon-group {
164
+ @include inline-flex;
165
+ @include items-center;
166
+ gap: var(--gap);
167
+ // Compact padding: tight vertical to keep total height close to a
168
+ // regular form-input despite the icon's fixed size.
169
+ --padding-y: var(--size-xs);
170
+ --padding-x: var(--size-sm);
171
+ padding: var(--padding-y) var(--padding-x);
172
+ background: var(--color-bg);
173
+ @include border;
174
+ border-radius: var(--radius);
175
+ @include transition;
176
+ font-size: var(--font-size);
177
+ line-height: var(--line-height);
178
+ // Wrapping-label reset: undo form-label defaults that would otherwise leak.
179
+ margin-bottom: 0;
180
+
181
+ &:focus-within {
182
+ @include _form-focus-style;
183
+ }
184
+
185
+ > .ln-icon {
186
+ @include flex-shrink-0;
187
+ --color-fg: var(--fg-subtle);
188
+ color: var(--color-fg);
189
+ }
190
+
191
+ > input {
192
+ // Strip container chrome inherited from form-input — label owns it now.
193
+ padding: 0;
194
+ @include border-none;
195
+ background: transparent;
196
+ outline: none;
197
+ box-shadow: none;
198
+
199
+ // Tighten leading for a single-line control. form-input inherits
200
+ // --lh-body-md (1.6) which is tuned for prose readability, not form
201
+ // controls — that leaves ~7px of wasted leading above and below the
202
+ // glyph inside the input's own line box, which compounds with the
203
+ // label's padding and makes icon-group feel airy. 1.25 keeps
204
+ // descenders (g/p/j/y) safe and brings the label down to ~36px total.
205
+ line-height: 1.25;
206
+
207
+ // Take the remaining flex space. width: auto cancels form-input's
208
+ // width: 100%; flex-1 + min-width: 0 lets the field shrink below
209
+ // its intrinsic min-content inside a narrow label.
210
+ width: auto;
211
+ @include flex-1;
212
+ min-width: 0;
213
+
214
+ &:focus-visible {
215
+ border-color: transparent;
216
+ box-shadow: none;
217
+ }
218
+
219
+ // Hide clear button when input is empty
220
+ &:placeholder-shown ~ [data-ln-search-clear] {
221
+ @include hidden;
222
+ }
223
+ }
224
+
225
+ > [data-ln-search-clear] {
226
+ @include flex-shrink-0;
227
+ --padding-y: var(--size-2xs);
228
+ --padding-x: var(--size-2xs);
229
+ padding: var(--padding-y) var(--padding-x);
230
+ @include cursor-pointer;
231
+ --color-fg: var(--fg-subtle);
232
+ color: var(--color-fg);
233
+ @include transition-colors;
234
+
235
+ &:hover {
236
+ color: var(--color-fg);
237
+ }
238
+ }
239
+ }
240
+
241
+ // ─── Checkbox & Radio ───────────────────────────────────────────
242
+
243
+ @mixin form-check {
244
+ appearance: none;
245
+ @include size(1.25rem);
246
+ padding: 0;
247
+ @include flex-shrink-0;
248
+ // Non-standard 1.5px stroke — optical weight at small checkbox size.
249
+ border: 1.5px solid var(--color-border);
250
+ background: var(--color-bg);
251
+ @include cursor-pointer;
252
+ @include transition;
253
+ vertical-align: middle;
254
+
255
+ &:checked {
256
+ background-color: var(--color-accent);
257
+ border-color: var(--color-accent);
258
+ // Inline SVG: native <input type="checkbox"> cannot contain child elements,
259
+ // so a data URI is the only way to embed a checkmark here.
260
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E");
261
+ background-size: 0.75rem;
262
+ background-position: center;
263
+ background-repeat: no-repeat;
264
+ }
265
+
266
+ &:focus-visible {
267
+ @include focus-ring;
268
+ }
269
+
270
+ &:disabled {
271
+ @include opacity-50;
272
+ @include cursor-not-allowed;
273
+ }
274
+ }
275
+
276
+ @mixin form-checkbox {
277
+ @include form-check;
278
+ @include rounded-sm;
279
+ }
280
+
281
+ @mixin form-radio {
282
+ @include form-check;
283
+ border-radius: 50%;
284
+
285
+ &:checked {
286
+ background-image: none;
287
+ box-shadow: inset 0 0 0 3px var(--color-bg);
288
+ }
289
+ }
290
+
291
+ // ─── Pill outline — base label (structure + visible border) ────
292
+ //
293
+ // Self-contained outline variant: transparent background, visible native
294
+ // input indicator, accent border + accent text on checked.
295
+ //
296
+ // Must be idempotent when re-applied over `@include pill` (the filled
297
+ // default installed globally by components/_form.scss on fieldset pills).
298
+ // Each rule below resets a behavior that `pill` adds: bg-sunken fill,
299
+ // hidden input, accent-fg text on checked.
300
+
301
+ @mixin pill-outline {
302
+ @include flex;
303
+ @include items-center;
304
+ gap: var(--gap);
305
+ --padding-y: var(--size-sm);
306
+ --padding-x: var(--size-md);
307
+ padding: var(--padding-y) var(--padding-x);
308
+ @include cursor-pointer;
309
+ @include transition;
310
+ @include text-sm;
311
+ @include font-medium;
312
+ margin-bottom: 0;
313
+ border-radius: var(--radius);
314
+ border: var(--border-width) solid var(--color-border);
315
+ // Reset pill's filled bg. Outline = transparent surface.
316
+ background: transparent;
317
+ position: relative;
318
+
319
+ // Restore the native input indicator. `display: revert` returns the
320
+ // UA default (inline-block for checkbox/radio) and cleanly undoes
321
+ // pill's `display: none` when pill-outline is applied as an override.
322
+ > input { display: revert; }
323
+
324
+ &:has(> input:checked) {
325
+ border-color: var(--color-accent);
326
+ color: var(--color-accent);
327
+ // Reset pill's accent fill on checked — outline stays transparent.
328
+ background: transparent;
329
+ z-index: 1;
330
+ }
331
+
332
+ &:hover:not(:has(> input:checked)) {
333
+ border-color: var(--color-accent);
334
+ z-index: 1;
335
+ }
336
+ }
337
+
338
+ // ─── Pill filled — extends pill-outline, hides input ───────────
339
+
340
+ @mixin pill {
341
+ @include pill-outline;
342
+ --color-bg: var(--bg-sunken);
343
+ // Derive hover from --color-accent at this scope (label element).
344
+ // Lets .error/.success/.warning/.info parents cascade to the checked-
345
+ // state hover via the .success { --color-primary } chain. See
346
+ // _btn.scss for the syntax rationale (number form, not percentage).
347
+ --color-accent-hover: hsl(from var(--color-accent) h s calc(l - 8));
348
+
349
+ background-color: var(--color-bg);
350
+ border-color: transparent;
351
+
352
+ > input { display: none; }
353
+
354
+ &:has(> input:checked) {
355
+ background-color: var(--color-accent);
356
+ color: var(--color-accent-fg);
357
+ }
358
+
359
+ &:hover:not(:has(> input:checked)) {
360
+ border-color: var(--color-border);
361
+ }
362
+
363
+ &:hover:has(> input:checked) {
364
+ background-color: var(--color-accent-hover);
365
+ }
366
+ }
367
+
368
+ @mixin pill-group {
369
+ @include inline-flex;
370
+
371
+ li label {
372
+ border-radius: 0;
373
+ }
374
+
375
+ li:first-child label {
376
+ border-radius: var(--radius) 0 0 var(--radius);
377
+ }
378
+
379
+ li:last-child label {
380
+ border-radius: 0 var(--radius) var(--radius) 0;
381
+ }
382
+
383
+ li:only-child label {
384
+ border-radius: var(--radius);
385
+ }
386
+ }
387
+
388
+ // ─── Pills — GCD for joined-pill checkbox/radio lists ──────────
389
+ // Joined-group filled pills: pill on each <li><label><input>, with
390
+ // joined-corner border-radius (square middles, partial-radius first/
391
+ // last) inlined here rather than composed via pill-group. Inlining
392
+ // is required because pill-group's `li label` selectors (0,1,5) are
393
+ // out-specificityed by pill's :has selector (0,2,5), which would
394
+ // reassert full radius on every pill. The selectors below match
395
+ // pill's :has shape so source-order/specificity resolves cleanly.
396
+ // Apply to a <ul> whose direct children are <li><label><input> pairs.
397
+ //
398
+ // Usage: project SCSS applies this on its own ul selector:
399
+ //
400
+ // #my-filter-pills { @include pills; }
401
+ //
402
+ // where the structure is:
403
+ //
404
+ // <ul id="my-filter-pills">
405
+ // <li><label><input type="checkbox"> Label</label></li>
406
+ // ...
407
+ // </ul>
408
+
409
+ @mixin pills {
410
+ @include inline-flex;
411
+
412
+ > li > label:has(> input[type="checkbox"]),
413
+ > li > label:has(> input[type="radio"]) {
414
+ @include pill;
415
+ border-radius: 0;
416
+ }
417
+
418
+ > li:first-child > label:has(> input[type="checkbox"]),
419
+ > li:first-child > label:has(> input[type="radio"]) {
420
+ border-radius: var(--radius) 0 0 var(--radius);
421
+ }
422
+
423
+ > li:last-child > label:has(> input[type="checkbox"]),
424
+ > li:last-child > label:has(> input[type="radio"]) {
425
+ border-radius: 0 var(--radius) var(--radius) 0;
426
+ }
427
+
428
+ > li:only-child > label:has(> input[type="checkbox"]),
429
+ > li:only-child > label:has(> input[type="radio"]) {
430
+ border-radius: var(--radius);
431
+ }
432
+ }
433
+
434
+ @mixin pills-outline {
435
+ @include inline-flex;
436
+
437
+ > li {
438
+ position: relative;
439
+
440
+ &:not(:first-child) {
441
+ margin-left: calc(var(--border-width) * -1);
442
+ }
443
+
444
+ &:hover,
445
+ &:has(> label > input:checked) {
446
+ z-index: 1;
447
+ }
448
+ }
449
+
450
+ > li > label:has(> input[type="checkbox"]),
451
+ > li > label:has(> input[type="radio"]) {
452
+ @include pill-outline;
453
+ border-radius: 0;
454
+ }
455
+
456
+ > li:first-child > label:has(> input[type="checkbox"]),
457
+ > li:first-child > label:has(> input[type="radio"]) {
458
+ border-radius: var(--radius) 0 0 var(--radius);
459
+ }
460
+
461
+ > li:last-child > label:has(> input[type="checkbox"]),
462
+ > li:last-child > label:has(> input[type="radio"]) {
463
+ border-radius: 0 var(--radius) var(--radius) 0;
464
+ }
465
+
466
+ > li:only-child > label:has(> input[type="checkbox"]),
467
+ > li:only-child > label:has(> input[type="radio"]) {
468
+ border-radius: var(--radius);
469
+ }
470
+ }
471
+
472
+ @mixin toggle-switch {
473
+ appearance: none;
474
+ position: relative;
475
+ width: 2.5rem;
476
+ height: 1.5rem;
477
+ --color-bg: var(--border-strong);
478
+ background: var(--color-bg);
479
+ border-radius: var(--radius-full);
480
+ cursor: pointer;
481
+ vertical-align: middle;
482
+ flex-shrink: 0;
483
+ border: none;
484
+ transition: none;
485
+
486
+ @include motion-safe {
487
+ transition: background-color var(--transition-fast);
488
+ }
489
+
490
+ &::before {
491
+ content: '';
492
+ position: absolute;
493
+ top: 2px;
494
+ left: 2px;
495
+ width: 1.25rem;
496
+ height: 1.25rem;
497
+ background: hsl(var(--color-white));
498
+ border-radius: 50%;
499
+ --shadow: var(--shadow-resting);
500
+ box-shadow: var(--shadow);
501
+
502
+ @include motion-safe {
503
+ transition: transform var(--transition-fast);
504
+ }
505
+ }
506
+
507
+ &:checked {
508
+ background: var(--color-accent);
509
+
510
+ &::before {
511
+ transform: translateX(1rem);
512
+ }
513
+ }
514
+
515
+ &:focus-visible {
516
+ @include focus-ring;
517
+ }
518
+
519
+ &:disabled {
520
+ opacity: 0.5;
521
+ cursor: not-allowed;
522
+ }
523
+ }
524
+
525
+ @mixin pill-switch {
526
+ @include row(var(--size-sm));
527
+ @include cursor-pointer;
528
+ @include text-sm;
529
+ @include font-medium;
530
+ color: var(--color-fg);
531
+ margin-bottom: 0;
532
+
533
+ > input[type="checkbox"] {
534
+ @include toggle-switch;
535
+ }
536
+ }
537
+
538
+ @mixin pills-switch {
539
+ @include stack(var(--size-sm));
540
+ list-style: none;
541
+ padding: 0;
542
+ margin: 0;
543
+
544
+ > li > label {
545
+ @include pill-switch;
546
+ }
547
+ }
548
+
549
+ // ─── Check list outline — vertical list with outlined labels ───
550
+ // <ul><li><label><input> — same structure as pill-group but vertical.
551
+ // List reset + pill-outline on labels (visible input, bordered).
552
+ //
553
+ // Usage:
554
+ // #dept-filter { @include check-list-outline; }
555
+
556
+ @mixin check-list-outline {
557
+ li label { @include pill-outline; }
558
+ }
559
+
560
+ // ─── Check list filled — vertical list with filled labels ──────
561
+ // Extends check-list-outline: hides input, fills bg on checked.
562
+ //
563
+ // Usage:
564
+ // #dept-filter { @include check-list; }
565
+
566
+ @mixin check-list {
567
+ @include check-list-outline;
568
+
569
+ li label { @include pill; }
570
+ }
571
+
572
+ // ─── Validation states ─────────────────────────────────────────
573
+ // Status colors (error/success) stay raw — no logical --color-status
574
+ // token in scope (see parent plan §6.2).
575
+
576
+ @mixin form-validate-invalid {
577
+ border-color: hsl(var(--color-error));
578
+
579
+ &:focus-visible {
580
+ border-color: hsl(var(--color-error));
581
+ box-shadow: 0 0 0 3px hsl(var(--color-error) / 0.15);
582
+ }
583
+ }
584
+
585
+ @mixin form-validate-valid {
586
+ border-color: hsl(var(--color-success));
587
+
588
+ &:focus-visible {
589
+ border-color: hsl(var(--color-success));
590
+ box-shadow: 0 0 0 3px hsl(var(--color-success) / 0.15);
591
+ }
592
+ }
593
+
594
+ @mixin form-validate-errors {
595
+ // Structural error-list margin (not shell rhythm).
596
+ --margin-block: var(--size-xs);
597
+ margin-top: var(--margin-block);
598
+ font-size: var(--text-caption);
599
+ line-height: var(--lh-caption);
600
+ color: hsl(var(--color-error));
601
+ }
@@ -0,0 +1,58 @@
1
+ // Mixins index — @forward all in correct order
2
+ // Primitives first, composites after (they depend on primitives)
3
+
4
+ // Primitives
5
+ @forward 'spacing';
6
+ @forward 'breakpoints';
7
+ @forward 'display';
8
+ @forward 'sizing';
9
+ @forward 'typography';
10
+ @forward 'colors';
11
+ @forward 'borders';
12
+ @forward 'shadows';
13
+ @forward 'transitions';
14
+ @forward 'motion';
15
+ @forward 'position';
16
+ @forward 'interaction';
17
+ @forward 'focus';
18
+
19
+ // Composites (depend on primitives)
20
+ @forward 'layout';
21
+ @forward 'collapsible';
22
+ @forward 'accordion';
23
+ @forward 'card';
24
+ @forward 'chip';
25
+ @forward 'nav';
26
+ @forward 'btn';
27
+ @forward 'form';
28
+ @forward 'modal';
29
+ @forward 'breadcrumbs';
30
+ @forward 'loader';
31
+ @forward 'tabs';
32
+ @forward 'table';
33
+ @forward 'avatar';
34
+ @forward 'footer';
35
+ @forward 'sidebar';
36
+ @forward 'app-shell';
37
+ @forward 'status-badge';
38
+ @forward 'alert';
39
+ @forward 'empty-state';
40
+ @forward 'page-header';
41
+ @forward 'stepper';
42
+ @forward 'timeline';
43
+ @forward 'stat-card';
44
+ @forward 'prose';
45
+ @forward 'kbd';
46
+ @forward 'tooltip';
47
+ @forward 'popover';
48
+ @forward 'progress';
49
+ @forward 'link';
50
+ @forward 'confirm';
51
+ @forward 'circular-progress';
52
+ @forward 'dropdown';
53
+ @forward 'toast';
54
+ @forward 'upload';
55
+ @forward 'translations';
56
+ @forward 'data-table';
57
+ @forward 'ln-table';
58
+ @forward 'ajax';
@@ -0,0 +1,15 @@
1
+ // Cursor, Interaction & Z-Index
2
+
3
+ @mixin cursor-pointer { cursor: pointer; }
4
+ @mixin cursor-not-allowed { cursor: not-allowed; }
5
+ @mixin select-none { user-select: none; }
6
+ @mixin opacity-50 { opacity: 0.5; }
7
+
8
+ // Z-Index — references --z-* tokens
9
+ @mixin z-dropdown { z-index: var(--z-dropdown); }
10
+ @mixin z-sticky { z-index: var(--z-sticky); }
11
+ @mixin z-overlay { z-index: var(--z-overlay); }
12
+ @mixin z-modal { z-index: var(--z-modal); }
13
+ @mixin z-toast { z-index: var(--z-toast); }
14
+
15
+ @mixin z($val) { z-index: $val; }
@@ -0,0 +1,22 @@
1
+ @use 'spacing' as *;
2
+ @use 'typography' as *;
3
+ @use 'colors' as *;
4
+ @use 'borders' as *;
5
+
6
+ @mixin kbd {
7
+ --color-bg: var(--bg-sunken);
8
+ display: inline-block;
9
+ --padding-y: var(--size-2xs);
10
+ --padding-x: var(--size-xs-up);
11
+ padding: var(--padding-y) var(--padding-x);
12
+ @include typography(caption);
13
+ font-family: var(--font-mono);
14
+ color: var(--color-fg);
15
+ background: var(--color-bg);
16
+ border: var(--border-width) solid var(--color-border);
17
+ border-bottom-width: 2px;
18
+ --radius: var(--radius-sm);
19
+ border-radius: var(--radius);
20
+ line-height: 1;
21
+ white-space: nowrap;
22
+ }