@wovenui/components 0.0.0-stage → 1.0.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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -2
  3. package/dist/components.json +4204 -0
  4. package/dist/llms-full.txt +2839 -0
  5. package/dist/wovenui-kit.css +5002 -0
  6. package/dist/wovenui-kit.css.map +1 -0
  7. package/dist/wovenui-kit.iife.js +532 -0
  8. package/dist/wovenui-kit.js +529 -0
  9. package/dist/wovenui-kit.min.css +2 -0
  10. package/dist/wovenui-kit.min.css.map +1 -0
  11. package/package.json +268 -4
  12. package/src/_close.scss +45 -0
  13. package/src/_helpers.scss +11 -0
  14. package/src/_kit.scss +124 -0
  15. package/src/_shared.scss +10 -0
  16. package/src/components/_accordion.scss +132 -0
  17. package/src/components/_alert.scss +161 -0
  18. package/src/components/_autocomplete.scss +129 -0
  19. package/src/components/_avatar-group.scss +80 -0
  20. package/src/components/_avatar.scss +134 -0
  21. package/src/components/_badge.scss +91 -0
  22. package/src/components/_breadcrumb.scss +115 -0
  23. package/src/components/_btn-group.scss +113 -0
  24. package/src/components/_button.scss +301 -0
  25. package/src/components/_card.scss +120 -0
  26. package/src/components/_checkbox.scss +168 -0
  27. package/src/components/_code-block.scss +78 -0
  28. package/src/components/_confirm-modal.scss +75 -0
  29. package/src/components/_data-table.scss +79 -0
  30. package/src/components/_desc-list.scss +180 -0
  31. package/src/components/_divider.scss +66 -0
  32. package/src/components/_dot.scss +115 -0
  33. package/src/components/_drawer.scss +200 -0
  34. package/src/components/_dropdown.scss +182 -0
  35. package/src/components/_empty-state.scss +146 -0
  36. package/src/components/_field-group.scss +68 -0
  37. package/src/components/_field-message.scss +75 -0
  38. package/src/components/_field.scss +127 -0
  39. package/src/components/_form-actions.scss +58 -0
  40. package/src/components/_info-row.scss +69 -0
  41. package/src/components/_input-group.scss +87 -0
  42. package/src/components/_input.scss +278 -0
  43. package/src/components/_list.scss +76 -0
  44. package/src/components/_modal.scss +168 -0
  45. package/src/components/_page-header.scss +85 -0
  46. package/src/components/_pagination.scss +138 -0
  47. package/src/components/_panel.scss +69 -0
  48. package/src/components/_popover.scss +154 -0
  49. package/src/components/_progress-circle.scss +123 -0
  50. package/src/components/_progress.scss +122 -0
  51. package/src/components/_radio.scss +162 -0
  52. package/src/components/_segmented.scss +183 -0
  53. package/src/components/_select.scss +154 -0
  54. package/src/components/_sidebar.scss +300 -0
  55. package/src/components/_skeleton.scss +90 -0
  56. package/src/components/_spinner.scss +84 -0
  57. package/src/components/_stat.scss +92 -0
  58. package/src/components/_table.scss +460 -0
  59. package/src/components/_tabs.scss +327 -0
  60. package/src/components/_tag.scss +143 -0
  61. package/src/components/_text.scss +42 -0
  62. package/src/components/_textarea.scss +57 -0
  63. package/src/components/_timeline.scss +196 -0
  64. package/src/components/_title.scss +41 -0
  65. package/src/components/_toast.scss +221 -0
  66. package/src/components/_toggle.scss +171 -0
  67. package/src/components/_toolbar.scss +74 -0
  68. package/src/components/_topbar.scss +204 -0
  69. package/src/entries/accordion.scss +4 -0
  70. package/src/entries/alert.scss +4 -0
  71. package/src/entries/autocomplete.scss +4 -0
  72. package/src/entries/avatar-group.scss +4 -0
  73. package/src/entries/avatar.scss +4 -0
  74. package/src/entries/badge.scss +4 -0
  75. package/src/entries/breadcrumb.scss +4 -0
  76. package/src/entries/btn-group.scss +4 -0
  77. package/src/entries/button.scss +4 -0
  78. package/src/entries/card.scss +4 -0
  79. package/src/entries/checkbox.scss +4 -0
  80. package/src/entries/code-block.scss +4 -0
  81. package/src/entries/confirm-modal.scss +4 -0
  82. package/src/entries/data-table.scss +4 -0
  83. package/src/entries/desc-list.scss +4 -0
  84. package/src/entries/divider.scss +4 -0
  85. package/src/entries/dot.scss +4 -0
  86. package/src/entries/drawer.scss +4 -0
  87. package/src/entries/dropdown.scss +4 -0
  88. package/src/entries/empty-state.scss +4 -0
  89. package/src/entries/field-group.scss +4 -0
  90. package/src/entries/field-message.scss +4 -0
  91. package/src/entries/field.scss +4 -0
  92. package/src/entries/form-actions.scss +4 -0
  93. package/src/entries/info-row.scss +4 -0
  94. package/src/entries/input-group.scss +4 -0
  95. package/src/entries/input.scss +4 -0
  96. package/src/entries/list.scss +4 -0
  97. package/src/entries/modal.scss +4 -0
  98. package/src/entries/page-header.scss +4 -0
  99. package/src/entries/pagination.scss +4 -0
  100. package/src/entries/panel.scss +4 -0
  101. package/src/entries/popover.scss +4 -0
  102. package/src/entries/progress-circle.scss +4 -0
  103. package/src/entries/progress.scss +4 -0
  104. package/src/entries/radio.scss +4 -0
  105. package/src/entries/segmented.scss +4 -0
  106. package/src/entries/select.scss +4 -0
  107. package/src/entries/sidebar.scss +4 -0
  108. package/src/entries/skeleton.scss +4 -0
  109. package/src/entries/spinner.scss +4 -0
  110. package/src/entries/stat.scss +4 -0
  111. package/src/entries/table.scss +4 -0
  112. package/src/entries/tabs.scss +4 -0
  113. package/src/entries/tag.scss +4 -0
  114. package/src/entries/text.scss +4 -0
  115. package/src/entries/textarea.scss +4 -0
  116. package/src/entries/timeline.scss +4 -0
  117. package/src/entries/title.scss +4 -0
  118. package/src/entries/toast.scss +4 -0
  119. package/src/entries/toggle.scss +4 -0
  120. package/src/entries/toolbar.scss +4 -0
  121. package/src/entries/topbar.scss +4 -0
  122. package/src/index.scss +7 -0
  123. package/src/js/autocomplete.js +86 -0
  124. package/src/js/data-table.js +141 -0
  125. package/src/js/index.js +34 -0
  126. package/src/js/input-clear.js +25 -0
  127. package/src/js/package.json +3 -0
  128. package/src/js/tabs.js +73 -0
  129. package/src/js/toast.js +161 -0
  130. package/src/js/util.js +21 -0
@@ -0,0 +1,300 @@
1
+ ////
2
+ //// @component wu-sidebar
3
+ //// Vertical navigation with sections, link lists and collapsible groups.
4
+ ////
5
+ //// Use a `nav` with an `aria-label`, and mark the current link with `aria-current="page"`. A collapsible group is a `details` with the `wu-sidebar-group` class and a `summary` with `wu-sidebar-toggle`; the toggle may also be a `button` with `aria-expanded`.
6
+ ////
7
+ //// On narrow screens put the sidebar in a `dialog` with `wu-drawer inline-start`, where it fills the drawer.
8
+ ////
9
+ //// Icon rail: add `collapsed`; each link holds an `svg` and a `wu-sidebar-label`, shown as a tooltip on hover or focus.
10
+ ////
11
+ //// @since 1.0.0
12
+ //// @needs none
13
+ //// @js none
14
+ //// @example html
15
+ //// <nav class="wu-sidebar" aria-label="Main">
16
+ //// <div class="wu-sidebar-section">
17
+ //// <h2 class="wu-sidebar-heading">Workspace</h2>
18
+ //// <ul class="wu-sidebar-list">
19
+ //// <li><a class="wu-sidebar-link" href="/dashboard" aria-current="page">Dashboard</a></li>
20
+ //// <li>
21
+ //// <details class="wu-sidebar-group" open>
22
+ //// <summary class="wu-sidebar-toggle">Projects</summary>
23
+ //// <ul class="wu-sidebar-list">
24
+ //// <li><a class="wu-sidebar-link" href="/projects/apollo">Apollo</a></li>
25
+ //// </ul>
26
+ //// </details>
27
+ //// </li>
28
+ //// </ul>
29
+ //// </div>
30
+ //// <div class="wu-sidebar-footer">Signed in as Sam</div>
31
+ //// </nav>
32
+ //// @example html
33
+ //// <nav class="wu-sidebar collapsed pinned" aria-label="Main">
34
+ //// <ul class="wu-sidebar-list">
35
+ //// <li>
36
+ //// <a class="wu-sidebar-link" href="/settings">
37
+ //// <svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" /></svg>
38
+ //// <span class="wu-sidebar-label">Settings</span>
39
+ //// </a>
40
+ //// </li>
41
+ //// </ul>
42
+ //// </nav>
43
+ //// @modifier pinned - Sticks beside scrolling content and scrolls on its own @since 1.0.0
44
+ //// @modifier collapsed - Icon rail: labels and headings are visually hidden, and a label shows as a tooltip on hover or focus @since 1.0.0
45
+ //// @modifier animated - Animates the width, the chevron and the opening of groups when the user allows motion; `wu-animated` on an ancestor does the same @since 1.0.0
46
+ //// @part wu-sidebar-header - Top row for a logo or title @since 1.0.0
47
+ //// @part wu-sidebar-footer - Bottom row, pushed to the end @since 1.0.0
48
+ //// @part wu-sidebar-section - Group of a heading and a list @since 1.0.0
49
+ //// @part wu-sidebar-heading - Small muted section heading @since 1.0.0
50
+ //// @part wu-sidebar-list - Vertical list of links; nested inside a group it is indented @since 1.0.0
51
+ //// @part wu-sidebar-group - A `details` element that collapses a nested list @since 1.0.0
52
+ //// @part wu-sidebar-toggle - Row that opens or closes a group, with a chevron @since 1.0.0
53
+ //// @part wu-sidebar-link - Navigation link row @since 1.0.0
54
+ //// @part wu-sidebar-label - Text label of a link; hidden and shown as a tooltip when collapsed @since 1.0.0
55
+ //// @variable --wu-sidebar--Width [16rem] - Sidebar width @since 1.0.0
56
+ //// @variable --wu-sidebar--CollapsedWidth [calc(1em + 3rem)] - Width of the icon rail @since 1.0.0
57
+ //// @state aria-current="page" - The current link: tinted fill and a bar at the inline start @since 1.0.0
58
+ //// @state open | aria-expanded="true" - The chevron points down @since 1.0.0
59
+ ////
60
+
61
+ @use "@wovenui/core/mixins" as woven;
62
+
63
+ .wu-sidebar {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: woven.token(space-4);
67
+ inline-size: var(--wu-sidebar--Width, #{woven.token(space-64)});
68
+ max-inline-size: 100%;
69
+ padding: woven.token(space-3);
70
+ border-inline-end: woven.token(border-width-1) solid woven.token(border-regular);
71
+ background: woven.token(bg-surface);
72
+ color: woven.token(text-regular);
73
+ font-size: woven.token(font-size-sm);
74
+ }
75
+
76
+ .wu-drawer > .wu-sidebar {
77
+ flex: 1;
78
+ inline-size: auto;
79
+ border-inline-end: 0;
80
+ overflow-y: auto;
81
+ }
82
+
83
+ // Sticks beside scrolling content and scrolls on its own
84
+ .wu-sidebar.pinned {
85
+ position: sticky;
86
+ inset-block-start: 0;
87
+ align-self: flex-start;
88
+ max-block-size: 100dvh;
89
+ overflow-y: auto;
90
+ overscroll-behavior: contain;
91
+ }
92
+
93
+ .wu-sidebar-header,
94
+ .wu-sidebar-footer {
95
+ display: flex;
96
+ align-items: center;
97
+ gap: woven.token(space-2);
98
+ padding-inline: woven.token(space-3);
99
+ }
100
+
101
+ .wu-sidebar-footer {
102
+ margin-block-start: auto;
103
+ }
104
+
105
+ .wu-sidebar-section {
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: woven.token(space-1);
109
+ }
110
+
111
+ .wu-sidebar-heading {
112
+ margin: 0;
113
+ padding-inline: woven.token(space-3);
114
+ color: woven.token(text-muted);
115
+ font-size: woven.token(font-size-xs);
116
+ font-weight: woven.token(font-weight-semibold);
117
+ line-height: woven.token(line-height-normal);
118
+ }
119
+
120
+ .wu-sidebar-list {
121
+ display: flex;
122
+ flex-direction: column;
123
+ gap: woven.token(space-1);
124
+ margin: 0;
125
+ padding: 0;
126
+ list-style: none;
127
+ }
128
+
129
+ .wu-sidebar-group > .wu-sidebar-list {
130
+ margin-block-start: woven.token(space-1);
131
+ padding-inline-start: woven.token(space-4);
132
+ }
133
+
134
+ // Link and group toggle share one row look; the toggle may also be a <button aria-expanded>
135
+ .wu-sidebar-link,
136
+ .wu-sidebar-toggle {
137
+ position: relative;
138
+ display: flex;
139
+ align-items: center;
140
+ gap: woven.token(space-2);
141
+ width: 100%;
142
+ min-height: woven.token(control-height-sm);
143
+ padding-block: 0;
144
+ padding-inline: woven.token(space-3);
145
+ border: 0;
146
+ border-radius: woven.token(radius-sm);
147
+ background: transparent;
148
+ color: woven.token(text-muted);
149
+ font: inherit;
150
+ font-weight: woven.token(font-weight-medium);
151
+ text-align: start;
152
+ text-decoration: none;
153
+ cursor: pointer;
154
+ transition-property: background-color, color;
155
+ transition-duration: woven.token(duration-150);
156
+ transition-timing-function: woven.token(ease-out);
157
+
158
+ &:hover {
159
+ background: woven.token(bg-hover);
160
+ color: woven.token(text-regular);
161
+ }
162
+
163
+ // Inset ring, so a scrolling sidebar does not clip it
164
+ &:focus-visible {
165
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
166
+ outline-offset: calc(-1 * #{woven.token(border-focus-width)});
167
+ }
168
+
169
+ > svg {
170
+ flex-shrink: 0;
171
+ width: 1em;
172
+ height: 1em;
173
+ }
174
+ }
175
+
176
+ // Current page: tint plus a bar, so color is not the only signal
177
+ .wu-sidebar-link[aria-current="page"] {
178
+ background: woven.token(bg-selected);
179
+ color: woven.token(text-brand);
180
+
181
+ &::before {
182
+ content: "";
183
+ position: absolute;
184
+ inset-block: woven.token(space-1);
185
+ inset-inline-start: 0;
186
+ width: woven.token(border-width-2);
187
+ border-radius: woven.token(radius-full);
188
+ background: woven.token(brand);
189
+ }
190
+ }
191
+
192
+ .wu-sidebar-toggle {
193
+ list-style: none;
194
+
195
+ &::-webkit-details-marker {
196
+ display: none;
197
+ }
198
+
199
+ // Chevron drawn with two borders: points to the end when closed, down when open
200
+ &::after {
201
+ content: "";
202
+ flex: none;
203
+ width: 0.4em;
204
+ height: 0.4em;
205
+ margin-inline-start: auto;
206
+ border-right: woven.token(border-width-2) solid currentcolor;
207
+ border-bottom: woven.token(border-width-2) solid currentcolor;
208
+ rotate: -45deg;
209
+ }
210
+
211
+ &:dir(rtl)::after {
212
+ rotate: 135deg;
213
+ }
214
+ }
215
+
216
+ .wu-sidebar-group[open] > .wu-sidebar-toggle::after,
217
+ .wu-sidebar-toggle[aria-expanded="true"]::after {
218
+ rotate: 45deg;
219
+ }
220
+
221
+ .wu-sidebar-group:has([aria-current="page"]) > .wu-sidebar-toggle {
222
+ color: woven.token(text-regular);
223
+ }
224
+
225
+ // Icon rail: labels hidden visually but kept for screen readers, shown as a tooltip at the inline end on hover or focus
226
+ .wu-sidebar.collapsed {
227
+ inline-size: var(--wu-sidebar--CollapsedWidth, calc(1em + 4 * #{woven.token(space-3)}));
228
+ overflow: visible;
229
+
230
+ .wu-sidebar-heading,
231
+ .wu-sidebar-label {
232
+ position: absolute;
233
+ width: 1px;
234
+ height: 1px;
235
+ overflow: hidden;
236
+ clip-path: inset(50%);
237
+ white-space: nowrap;
238
+ }
239
+
240
+ .wu-sidebar-group > .wu-sidebar-list {
241
+ padding-inline-start: 0;
242
+ }
243
+
244
+ .wu-sidebar-toggle::after {
245
+ display: none;
246
+ }
247
+
248
+ :is(.wu-sidebar-link, .wu-sidebar-toggle):is(:hover, :focus-visible) > .wu-sidebar-label {
249
+ inset-block-start: 50%;
250
+ inset-inline-start: 100%;
251
+ z-index: woven.token(z-tooltip);
252
+ width: auto;
253
+ height: auto;
254
+ margin-inline-start: woven.token(space-2);
255
+ padding-block: woven.token(space-1);
256
+ padding-inline: woven.token(space-2);
257
+ border-radius: woven.token(radius-xs);
258
+ background: woven.token(text-regular);
259
+ color: woven.token(text-inverted);
260
+ box-shadow: woven.token(shadow-md);
261
+ font-weight: woven.token(font-weight-normal);
262
+ clip-path: none;
263
+ pointer-events: none;
264
+ translate: 0 -50%;
265
+ }
266
+ }
267
+
268
+ // Opt-in motion: the chevron turns and groups slide open
269
+ @media (prefers-reduced-motion: no-preference) {
270
+ .wu-sidebar.animated,
271
+ .wu-animated .wu-sidebar {
272
+ .wu-sidebar-toggle::after {
273
+ transition: rotate woven.token(duration-200) woven.token(ease-out);
274
+ }
275
+ }
276
+
277
+ .wu-sidebar.animated,
278
+ .wu-animated .wu-sidebar {
279
+ transition: inline-size woven.token(duration-200) woven.token(ease-out);
280
+ }
281
+
282
+ @supports (interpolate-size: allow-keywords) and selector(::details-content) {
283
+ .wu-sidebar.animated,
284
+ .wu-animated .wu-sidebar {
285
+ interpolate-size: allow-keywords;
286
+
287
+ .wu-sidebar-group::details-content {
288
+ block-size: 0;
289
+ overflow: clip;
290
+ transition:
291
+ block-size woven.token(duration-200) woven.token(ease-out),
292
+ content-visibility woven.token(duration-200) allow-discrete;
293
+ }
294
+
295
+ .wu-sidebar-group[open]::details-content {
296
+ block-size: auto;
297
+ }
298
+ }
299
+ }
300
+ }
@@ -0,0 +1,90 @@
1
+ ////
2
+ //// @component wu-skeleton
3
+ //// Placeholder shapes shown while content loads.
4
+ ////
5
+ //// Placeholders are decorative: add `aria-hidden="true"` to each skeleton and `aria-busy="true"` to the region being loaded, plus visible or hidden Loading text. Remove both when the content arrives.
6
+ ////
7
+ //// The default shape is a block. Adjacent `text` skeletons stack with a gap, and a last `text` line is shorter.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <div aria-busy="true">
14
+ //// <span class="sr-only">Loading</span>
15
+ //// <div class="wu-skeleton circle animated" aria-hidden="true"></div>
16
+ //// <div class="wu-skeleton text animated" aria-hidden="true"></div>
17
+ //// <div class="wu-skeleton text animated" aria-hidden="true"></div>
18
+ //// </div>
19
+ //// @example html
20
+ //// <div class="wu-skeleton" aria-hidden="true" style="--wu-skeleton--Height: 8rem"></div>
21
+ //// @modifier text - One line of text: height of `1em` @since 1.0.0
22
+ //// @modifier circle - Round shape for an avatar @since 1.0.0
23
+ //// @modifier animated - Shimmer sweep, staggered across siblings where supported, when the user allows motion; `wu-animated` on an ancestor does the same @since 1.0.0
24
+ //// @variable --wu-skeleton--Width [100%] - Width; a last `text` line defaults to 60% @since 1.0.0
25
+ //// @variable --wu-skeleton--Height [6rem] - Height; `text` defaults to 1em @since 1.0.0
26
+ //// @variable --wu-skeleton--Size [2.5rem] - Width and height of a `circle` @since 1.0.0
27
+ ////
28
+
29
+ @use "@wovenui/core/mixins" as woven;
30
+
31
+ $base: color-mix(in srgb, #{woven.token(text-regular)} 10%, transparent);
32
+ $shine: color-mix(in srgb, #{woven.token(text-regular)} 6%, transparent);
33
+
34
+ .wu-skeleton {
35
+ display: block;
36
+ width: var(--wu-skeleton--Width, 100%);
37
+ height: var(--wu-skeleton--Height, #{woven.token(space-24)});
38
+ border-radius: woven.token(radius-md);
39
+ background: linear-gradient(90deg, transparent 25%, #{$shine} 50%, transparent 75%) 150% 0 / 200% 100% no-repeat #{$base};
40
+ }
41
+
42
+ .wu-skeleton.text {
43
+ height: var(--wu-skeleton--Height, 1em);
44
+ border-radius: woven.token(radius-sm);
45
+ }
46
+
47
+ .wu-skeleton.text + .wu-skeleton.text {
48
+ margin-block-start: woven.token(space-2);
49
+ }
50
+
51
+ // Shorter last line of a paragraph
52
+ .wu-skeleton.text:last-child:not(:first-child) {
53
+ width: var(--wu-skeleton--Width, 60%);
54
+ }
55
+
56
+ .wu-skeleton.circle {
57
+ flex-shrink: 0;
58
+ width: var(--wu-skeleton--Size, #{woven.token(space-10)});
59
+ height: var(--wu-skeleton--Size, #{woven.token(space-10)});
60
+ border-radius: woven.token(radius-full);
61
+ }
62
+
63
+ @media (forced-colors: active) {
64
+ .wu-skeleton {
65
+ border: woven.token(border-width-1) solid GrayText;
66
+ }
67
+ }
68
+
69
+ // Opt-in shimmer, a staggered wave where sibling-index() is supported; it sweeps in the reading direction
70
+ @media (prefers-reduced-motion: no-preference) {
71
+ .wu-skeleton.animated,
72
+ .wu-animated .wu-skeleton {
73
+ animation: wu-skeleton-shimmer 1.6s ease-in-out infinite;
74
+ animation-delay: calc((sibling-index() - 1) * 80ms);
75
+
76
+ &:dir(rtl) {
77
+ animation-direction: reverse;
78
+ }
79
+ }
80
+ }
81
+
82
+ @keyframes wu-skeleton-shimmer {
83
+ from {
84
+ background-position: 150% 0;
85
+ }
86
+
87
+ to {
88
+ background-position: -50% 0;
89
+ }
90
+ }
@@ -0,0 +1,84 @@
1
+ ////
2
+ //// @component wu-spinner
3
+ //// Loading ring in the current text color, with an overlay for busy regions.
4
+ ////
5
+ //// Pair it with `role="status"` and hidden text, or use `aria-hidden="true"` next to visible text. Reduced motion is handled by core reset.
6
+ ////
7
+ //// The overlay covers its parent, dims the content and centers a spinner with optional text. The parent becomes the positioning box unless it already has a position.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <span class="wu-spinner" role="status"><span class="sr-only">Loading</span></span>
14
+ //// <button class="wu-btn" type="button" aria-busy="true"><span class="wu-spinner sm" aria-hidden="true"></span> Saving</button>
15
+ //// @example html
16
+ //// <div aria-busy="true">
17
+ //// <div inert>Orders table</div>
18
+ //// <div class="wu-spinner-overlay"><span class="wu-spinner" aria-hidden="true"></span><span>Loading orders</span></div>
19
+ //// </div>
20
+ //// @modifier sm | lg - Smaller (1rem) or larger (2rem, thicker band) ring @since 1.0.0
21
+ //// @part wu-spinner-overlay - Layer that covers a busy region and centers a spinner with text @since 1.0.0
22
+ //// @variable --wu-spinner--Size [1.25rem] - Width and height of the ring @since 1.0.0
23
+ //// @variable --wu-spinner-overlay--Background [85% surface] - Overlay background @since 1.0.0
24
+ ////
25
+
26
+ @use "@wovenui/core/mixins" as woven;
27
+
28
+ .wu-spinner {
29
+ display: inline-block;
30
+ flex-shrink: 0;
31
+ width: var(--wu-spinner--Size, #{woven.token(space-5)});
32
+ height: var(--wu-spinner--Size, #{woven.token(space-5)});
33
+ border: woven.token(border-width-2) solid color-mix(in srgb, currentcolor 25%, transparent);
34
+ border-block-start-color: currentcolor;
35
+ border-radius: woven.token(radius-full);
36
+ vertical-align: middle;
37
+ animation: wu-spinner-rotate 0.75s linear infinite;
38
+ }
39
+
40
+ .wu-spinner.sm {
41
+ width: var(--wu-spinner--Size, #{woven.token(space-4)});
42
+ height: var(--wu-spinner--Size, #{woven.token(space-4)});
43
+ }
44
+
45
+ .wu-spinner.lg {
46
+ width: var(--wu-spinner--Size, #{woven.token(space-8)});
47
+ height: var(--wu-spinner--Size, #{woven.token(space-8)});
48
+ border-width: woven.token(border-width-4);
49
+ }
50
+
51
+ :where(:has(> .wu-spinner-overlay)) {
52
+ position: relative;
53
+ }
54
+
55
+ .wu-spinner-overlay {
56
+ position: absolute;
57
+ inset: 0;
58
+ z-index: woven.token(z-hover);
59
+ display: flex;
60
+ flex-direction: column;
61
+ align-items: center;
62
+ justify-content: center;
63
+ gap: woven.token(space-2);
64
+ padding: woven.token(space-4);
65
+ border-radius: inherit;
66
+ background: var(--wu-spinner-overlay--Background, color-mix(in srgb, #{woven.token(bg-surface)} 85%, transparent));
67
+ color: woven.token(text-muted);
68
+ font-size: woven.token(font-size-sm);
69
+ text-align: center;
70
+ }
71
+
72
+ .wu-spinner-overlay > :not(.wu-spinner) {
73
+ padding: woven.token(space-1) woven.token(space-3);
74
+ border-radius: woven.token(radius-full);
75
+ background: woven.token(bg-surface);
76
+ }
77
+
78
+ // No reduced-motion rule: core's reset already stops animation under reduced motion
79
+
80
+ @keyframes wu-spinner-rotate {
81
+ to {
82
+ rotate: 360deg;
83
+ }
84
+ }
@@ -0,0 +1,92 @@
1
+ ////
2
+ //// @component wu-stat
3
+ //// One figure with a label, a large value in tabular figures and an optional change line.
4
+ ////
5
+ //// Keep the signed number as text in the change line; the arrow and the color repeat it, they never replace it.
6
+ ////
7
+ //// @since 1.0.0
8
+ //// @needs none
9
+ //// @js none
10
+ //// @example html
11
+ //// <div class="wu-stat">
12
+ //// <span class="wu-stat-label">Active users</span>
13
+ //// <span class="wu-stat-value">1,284</span>
14
+ //// <span class="wu-stat-delta success" data-trend="up">+12% from last month</span>
15
+ //// </div>
16
+ //// @example html
17
+ //// <div class="wu-stat lg">
18
+ //// <span class="wu-stat-label">Open tickets</span>
19
+ //// <span class="wu-stat-value">37</span>
20
+ //// <span class="wu-stat-delta" data-trend="down">-5 this week</span>
21
+ //// </div>
22
+ //// @modifier sm | lg - Smaller or larger value @since 1.0.0
23
+ //// @modifier success | danger - On the change line: success or danger text color @since 1.0.0
24
+ //// @part wu-stat-label - Muted label @since 1.0.0
25
+ //// @part wu-stat-value - Large value @since 1.0.0
26
+ //// @part wu-stat-delta - Change line, muted by default @since 1.0.0
27
+ //// @state data-trend="up" | data-trend="down" - On the change line: draws an up or down arrow @since 1.0.0
28
+ ////
29
+
30
+ @use "@wovenui/core/mixins" as woven;
31
+
32
+ .wu-stat {
33
+ display: flex;
34
+ flex-direction: column;
35
+ gap: woven.token(space-1);
36
+ }
37
+
38
+ .wu-stat-label {
39
+ color: woven.token(text-muted);
40
+ font-size: woven.token(font-size-sm);
41
+ font-weight: woven.token(font-weight-medium);
42
+ }
43
+
44
+ .wu-stat-value {
45
+ color: woven.token(text-regular);
46
+ font-size: woven.token(font-size-3xl);
47
+ font-variant-numeric: tabular-nums;
48
+ font-weight: woven.token(font-weight-semibold);
49
+ line-height: woven.token(line-height-tight);
50
+ }
51
+
52
+ // Change line: keep the signed number as text; data-trend draws the arrow, role words set the tone
53
+ .wu-stat-delta {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: woven.token(space-1);
57
+ color: woven.token(text-muted);
58
+ font-size: woven.token(font-size-sm);
59
+ font-variant-numeric: tabular-nums;
60
+ font-weight: woven.token(font-weight-medium);
61
+ }
62
+
63
+ .wu-stat-delta[data-trend]::before {
64
+ content: "";
65
+ width: woven.token(space-2);
66
+ height: woven.token(space-2);
67
+ background: currentcolor;
68
+ }
69
+
70
+ .wu-stat-delta[data-trend="up"]::before {
71
+ clip-path: polygon(50% 0, 100% 100%, 0 100%);
72
+ }
73
+
74
+ .wu-stat-delta[data-trend="down"]::before {
75
+ clip-path: polygon(0 0, 100% 0, 50% 100%);
76
+ }
77
+
78
+ .wu-stat-delta.success {
79
+ color: woven.token(text-success);
80
+ }
81
+
82
+ .wu-stat-delta.danger {
83
+ color: woven.token(text-danger);
84
+ }
85
+
86
+ .wu-stat.sm .wu-stat-value {
87
+ font-size: woven.token(font-size-xl);
88
+ }
89
+
90
+ .wu-stat.lg .wu-stat-value {
91
+ font-size: woven.token(font-size-4xl);
92
+ }