@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,115 @@
1
+ ////
2
+ //// @component wu-dot
3
+ //// Small status dot for use next to status text, tinted by role.
4
+ ////
5
+ //// Add `aria-hidden="true"` and put the meaning in the text beside it. `success`, `warning` and `danger` also change the shape (check, triangle, octagon), so color is never the only signal.
6
+ ////
7
+ //// A corner dot pins to a `wu-badge-host`, the same wrapper a corner badge uses, and reads `--wu-badge--Offset` and `--wu-avatar--RingColor` from the badge and avatar.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <span class="wu-dot success" aria-hidden="true"></span> Online
14
+ //// @example html
15
+ //// <span class="wu-dot danger animated" aria-hidden="true"></span> Live
16
+ //// @example html
17
+ //// <span class="wu-badge-host">
18
+ //// <button class="wu-btn" type="button" aria-label="Inbox, new messages">Inbox</button>
19
+ //// <span class="wu-dot corner brand" aria-hidden="true"></span>
20
+ //// </span>
21
+ //// @modifier brand | success | danger | warning | info - Role color; success, warning and danger also change the shape @since 1.0.0
22
+ //// @modifier corner - Pins the dot to the top inline-end corner of a `wu-badge-host` @since 1.0.0
23
+ //// @modifier animated - Fading ring for live status when the user allows motion; `wu-animated` on an ancestor does the same @since 1.0.0
24
+ //// @variable --wu-dot--Size [var(--wu-space-2)] - Width and height of the dot @since 1.0.0
25
+ ////
26
+
27
+ @use "@wovenui/core/mixins" as woven;
28
+
29
+ .wu-dot {
30
+ display: inline-block;
31
+ position: relative;
32
+ flex-shrink: 0;
33
+ width: var(--wu-dot--Size, #{woven.token(space-2)});
34
+ height: var(--wu-dot--Size, #{woven.token(space-2)});
35
+ border-radius: woven.token(radius-full);
36
+ background: currentcolor;
37
+ color: woven.token(border-strong);
38
+ vertical-align: middle;
39
+ }
40
+
41
+ @each $role in "brand", "success", "danger", "warning", "info" {
42
+ .wu-dot.#{$role} {
43
+ color: woven.token($role);
44
+ }
45
+ }
46
+
47
+ // Corner: pinned to the top inline-end corner of a .wu-badge-host (same rule as in the badge)
48
+ .wu-badge-host {
49
+ display: inline-flex;
50
+ position: relative;
51
+ vertical-align: middle;
52
+ }
53
+
54
+ .wu-dot.corner {
55
+ position: absolute;
56
+ inset-block-start: 0;
57
+ inset-inline-end: 0;
58
+ translate: var(--wu-badge--Offset, 50%) -50%;
59
+
60
+ &:dir(rtl) {
61
+ translate: calc(-1 * var(--wu-badge--Offset, 50%)) -50%;
62
+ }
63
+
64
+ &:not(.success, .warning, .danger) {
65
+ box-shadow: 0 0 0 woven.token(border-width-2) var(--wu-avatar--RingColor, #{woven.token(bg-surface)});
66
+ }
67
+ }
68
+
69
+ // Shape markers from _shared.scss, drawn on ::before so the live ring is not clipped
70
+ .wu-dot:is(.success, .warning, .danger) {
71
+ border-radius: 0;
72
+ background: none;
73
+
74
+ &::before {
75
+ content: "";
76
+ position: absolute;
77
+ inset: 0;
78
+ background: currentcolor;
79
+ }
80
+ }
81
+
82
+ .wu-dot.success::before,
83
+ .wu-dot.success::after {
84
+ clip-path: var(--wu-kit-marker-check);
85
+ }
86
+
87
+ .wu-dot.warning::before,
88
+ .wu-dot.warning::after {
89
+ clip-path: var(--wu-kit-marker-triangle);
90
+ }
91
+
92
+ .wu-dot.danger::before,
93
+ .wu-dot.danger::after {
94
+ clip-path: var(--wu-kit-marker-octagon);
95
+ }
96
+
97
+ // Opt-in motion: a fading ring for live status
98
+ @media (prefers-reduced-motion: no-preference) {
99
+ .wu-dot.animated::after,
100
+ .wu-animated .wu-dot::after {
101
+ content: "";
102
+ position: absolute;
103
+ inset: 0;
104
+ border-radius: inherit;
105
+ background: currentcolor;
106
+ animation: wu-dot-ping 1.5s woven.token(ease-out) infinite;
107
+ }
108
+ }
109
+
110
+ @keyframes wu-dot-ping {
111
+ to {
112
+ opacity: 0;
113
+ scale: 2.5;
114
+ }
115
+ }
@@ -0,0 +1,200 @@
1
+ ////
2
+ //// @component wu-drawer
3
+ //// Side panel on the native `dialog` element, docked to the inline end, the inline start or the bottom.
4
+ ////
5
+ //// Opens with invoker commands (`commandfor` and `command`), no script. With `show-modal` the page does not scroll while it is open; the page keeps its scrollbar gutter so it does not shift. `show` opens it non-modal and leaves the page alone.
6
+ ////
7
+ //// Docks to the inline end by default; `inline-start` or `bottom` moves it. Right-to-left pages mirror the slide.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <button class="wu-btn" type="button" commandfor="d1" command="show-modal">Filters</button>
14
+ //// <dialog class="wu-drawer" id="d1" aria-labelledby="d1-title" closedby="any">
15
+ //// <header class="wu-drawer-header">
16
+ //// <h2 class="wu-drawer-title" id="d1-title">Filters</h2>
17
+ //// <button class="wu-drawer-close" type="button" commandfor="d1" command="close" aria-label="Close"></button>
18
+ //// </header>
19
+ //// <div class="wu-drawer-body">Content</div>
20
+ //// <form class="wu-drawer-footer" method="dialog">
21
+ //// <button class="wu-btn brand">Apply</button>
22
+ //// </form>
23
+ //// </dialog>
24
+ //// @example html
25
+ //// <dialog class="wu-drawer bottom animated" id="d2" aria-label="Details" closedby="any">
26
+ //// <div class="wu-drawer-body">Content</div>
27
+ //// </dialog>
28
+ //// @modifier inline-start - Docks to the inline start instead of the inline end @since 1.0.0
29
+ //// @modifier bottom - Docks to the bottom edge with rounded top corners, up to `--wu-drawer--MaxHeight` @since 1.0.0
30
+ //// @modifier animated - Slides in from its edge and fades the backdrop when the user allows motion; `wu-animated` on an ancestor does the same @since 1.0.0
31
+ //// @part wu-drawer-header - Top row with the title and close button @since 1.0.0
32
+ //// @part wu-drawer-title - Heading in the header @since 1.0.0
33
+ //// @part wu-drawer-body - Scrolling content area that fills the drawer @since 1.0.0
34
+ //// @part wu-drawer-footer - Bottom row for actions, aligned to the inline end @since 1.0.0
35
+ //// @part wu-drawer-close - Close button drawn by the shared close style; give it an `aria-label` @since 1.0.0
36
+ //// @variable --wu-drawer--Width [24rem] - Drawer width when docked to a side @since 1.0.0
37
+ //// @variable --wu-drawer--MaxHeight [75dvh] - Maximum height of a bottom drawer @since 1.0.0
38
+ //// @variable --wu-drawer--Backdrop [50% black, 70% in dark themes] - Color behind a modal drawer @since 1.0.0
39
+ //// @state open - Shown as a flex column so the body scrolls between header and footer @since 1.0.0
40
+ //// @state :modal - Locks page scroll while the drawer is open with `show-modal` @since 1.0.0
41
+ ////
42
+
43
+ @use "@wovenui/core/mixins" as woven;
44
+ @use "../close";
45
+
46
+ .wu-drawer {
47
+ position: fixed;
48
+ inset-block: 0;
49
+ inset-inline: auto 0;
50
+ inline-size: var(--wu-drawer--Width, 24rem);
51
+ max-inline-size: calc(100% - #{woven.token(space-8)});
52
+ block-size: auto;
53
+ max-block-size: none;
54
+ margin: 0;
55
+ padding: 0;
56
+ border: 0;
57
+ border-inline-start: woven.token(border-width-1) solid woven.token(border-regular);
58
+ background: woven.token(bg-surface);
59
+ color: woven.token(text-regular);
60
+ box-shadow: woven.token(shadow-xl);
61
+ z-index: woven.token(z-drawer);
62
+
63
+ &[open] {
64
+ display: flex;
65
+ flex-direction: column;
66
+ }
67
+
68
+ &::backdrop {
69
+ background: var(--wu-drawer--Backdrop, color-mix(in srgb, #{woven.token(black)} 50%, transparent));
70
+
71
+ // Stronger in dark themes, so the drawer stands apart
72
+ @include woven.dark-mode {
73
+ background: var(--wu-drawer--Backdrop, color-mix(in srgb, #{woven.token(black)} 70%, transparent));
74
+ }
75
+ }
76
+ }
77
+
78
+ // Page scroll lock while one is open with showModal(); show() opens it non-modal and leaves the page alone.
79
+ // The gutter keeps the page from shifting when its scrollbar goes away.
80
+ :root:has(.wu-drawer:modal) {
81
+ overflow: hidden;
82
+ scrollbar-gutter: stable;
83
+ }
84
+
85
+ .wu-drawer.inline-start {
86
+ inset-inline: 0 auto;
87
+ border-inline-start: 0;
88
+ border-inline-end: woven.token(border-width-1) solid woven.token(border-regular);
89
+ }
90
+
91
+ .wu-drawer.bottom {
92
+ inset-block: auto 0;
93
+ inset-inline: 0;
94
+ inline-size: auto;
95
+ max-inline-size: none;
96
+ max-block-size: var(--wu-drawer--MaxHeight, 75dvh);
97
+ border-inline-start: 0;
98
+ border-block-start: woven.token(border-width-1) solid woven.token(border-regular);
99
+ border-start-start-radius: woven.token(radius-lg);
100
+ border-start-end-radius: woven.token(radius-lg);
101
+ padding-block-end: env(safe-area-inset-bottom, 0);
102
+ }
103
+
104
+ .wu-drawer-header,
105
+ .wu-drawer-footer {
106
+ display: flex;
107
+ flex: none;
108
+ align-items: center;
109
+ gap: woven.token(space-2);
110
+ margin: 0;
111
+ padding-block: woven.token(space-4);
112
+ padding-inline: woven.token(space-5);
113
+ }
114
+
115
+ .wu-drawer-header {
116
+ justify-content: space-between;
117
+ border-block-end: woven.token(border-width-1) solid woven.token(border-regular);
118
+ }
119
+
120
+ .wu-drawer-title {
121
+ margin: 0;
122
+ font-size: woven.token(font-size-lg);
123
+ font-weight: woven.token(font-weight-semibold);
124
+ line-height: woven.token(line-height-tight);
125
+ }
126
+
127
+ .wu-drawer-body {
128
+ flex: 1 1 auto;
129
+ min-block-size: 0;
130
+ padding: woven.token(space-5);
131
+ overflow-y: auto;
132
+ overscroll-behavior: contain;
133
+ }
134
+
135
+ .wu-drawer-footer {
136
+ flex-wrap: wrap;
137
+ justify-content: flex-end;
138
+ border-block-start: woven.token(border-width-1) solid woven.token(border-regular);
139
+ }
140
+
141
+ .wu-drawer-close {
142
+ @include close.button;
143
+
144
+ margin-inline-end: calc(-1 * #{woven.token(space-2)});
145
+ }
146
+
147
+ // Off-screen position for the slide, mirrored in right-to-left pages
148
+ @mixin -offscreen {
149
+ translate: 100% 0;
150
+
151
+ &:dir(rtl),
152
+ &.inline-start {
153
+ translate: -100% 0;
154
+ }
155
+
156
+ &.inline-start:dir(rtl) {
157
+ translate: 100% 0;
158
+ }
159
+
160
+ &.bottom {
161
+ translate: 0 100%;
162
+ }
163
+ }
164
+
165
+ // Opt-in motion: the drawer slides in from its edge and back out, the backdrop fades
166
+ @media (prefers-reduced-motion: no-preference) {
167
+ .wu-drawer.animated,
168
+ .wu-animated .wu-drawer {
169
+ transition:
170
+ translate woven.token(duration-300) woven.token(ease-out),
171
+ display woven.token(duration-300) allow-discrete,
172
+ overlay woven.token(duration-300) allow-discrete;
173
+
174
+ &:not([open]) {
175
+ @include -offscreen;
176
+ }
177
+
178
+ &[open] {
179
+ @starting-style {
180
+ @include -offscreen;
181
+ }
182
+ }
183
+
184
+ &::backdrop {
185
+ opacity: 0;
186
+ transition:
187
+ opacity woven.token(duration-300) woven.token(ease-out),
188
+ display woven.token(duration-300) allow-discrete,
189
+ overlay woven.token(duration-300) allow-discrete;
190
+ }
191
+
192
+ &[open]::backdrop {
193
+ opacity: 1;
194
+
195
+ @starting-style {
196
+ opacity: 0;
197
+ }
198
+ }
199
+ }
200
+ }
@@ -0,0 +1,182 @@
1
+ ////
2
+ //// @component wu-dropdown
3
+ //// Menu of links and buttons in a native popover, anchored under its trigger.
4
+ ////
5
+ //// A button with `popovertarget` opens it; `commandfor` with `command="toggle-popover"` works as the trigger too. Esc and a click outside close it. `popovertargetaction="hide"` on a button item closes the menu after its click handler runs.
6
+ ////
7
+ //// Opens under the trigger where anchor positioning exists and flips when there is no room; elsewhere it is centered in the viewport. Engines without implicit anchors need `anchor-name` on the trigger and `position-anchor` on the menu, with the same name.
8
+ ////
9
+ //// `inline-end` aligns the menu's inline end with the trigger's, as for the caret of a split button.
10
+ ////
11
+ //// @since 1.0.0
12
+ //// @needs none
13
+ //// @js none
14
+ //// @example html
15
+ //// <button class="wu-btn caret" type="button" popovertarget="menu1">Actions</button>
16
+ //// <div class="wu-dropdown" id="menu1" popover>
17
+ //// <a class="wu-dropdown-item" href="/edit">Edit</a>
18
+ //// <button class="wu-dropdown-item" type="button" popovertarget="menu1" popovertargetaction="hide">Duplicate</button>
19
+ //// <hr class="wu-dropdown-separator">
20
+ //// <button class="wu-dropdown-item danger" type="button" popovertarget="menu1" popovertargetaction="hide">Delete</button>
21
+ //// </div>
22
+ //// @modifier inline-end - Aligns the menu's inline end with the trigger's inline end @since 1.0.0
23
+ //// @modifier danger - On an item: danger text and a danger hover tint @since 1.0.0
24
+ //// @modifier animated - Fades and slides in and out when the user allows motion; `wu-animated` on an ancestor does the same @since 1.0.0
25
+ //// @part wu-dropdown-item - Link or button row in the menu; an `svg` child is sized as an icon @since 1.0.0
26
+ //// @part wu-dropdown-separator - Line between groups of items; use an `hr` @since 1.0.0
27
+ //// @variable --wu-dropdown--MinWidth [10rem] - Minimum menu width @since 1.0.0
28
+ //// @variable --wu-dropdown--MaxHeight [20rem] - Maximum menu height before it scrolls @since 1.0.0
29
+ //// @state disabled | aria-disabled="true" - On an item: half opacity, no pointer events @since 1.0.0
30
+ //// @state :popover-open - A plain trigger button right before the menu takes a hover look while it is open @since 1.0.0
31
+ ////
32
+
33
+ @use "@wovenui/core/mixins" as woven;
34
+
35
+ .wu-dropdown {
36
+ inset: 0;
37
+ inline-size: fit-content;
38
+ block-size: fit-content;
39
+ min-inline-size: var(--wu-dropdown--MinWidth, 10rem);
40
+ max-block-size: var(--wu-dropdown--MaxHeight, 20rem);
41
+ margin: auto;
42
+ padding: woven.token(space-1);
43
+ border: woven.token(border-width-1) solid woven.token(border-regular);
44
+ border-radius: woven.token(radius-md);
45
+ background: woven.token(bg-raised);
46
+ color: woven.token(text-regular);
47
+ font-size: woven.token(font-size-sm);
48
+ box-shadow: woven.token(shadow-lg);
49
+ overflow-y: auto;
50
+ overscroll-behavior: contain;
51
+ z-index: woven.token(z-dropdown);
52
+ }
53
+
54
+ @supports (anchor-name: --x) {
55
+ .wu-dropdown {
56
+ // auto keeps the implicit anchor, which normal drops without position-area
57
+ position-anchor: auto;
58
+ inset: auto;
59
+ inset-block-start: anchor(end);
60
+ inset-inline-start: anchor(self-start);
61
+ margin: 0;
62
+ margin-block: woven.token(space-1);
63
+ position-try-fallbacks:
64
+ flip-block,
65
+ flip-inline,
66
+ flip-block flip-inline;
67
+ }
68
+
69
+ // A split button's caret ends the group, so the menu ends with the group too
70
+ .wu-dropdown.inline-end {
71
+ inset-inline: auto anchor(self-end);
72
+ }
73
+ }
74
+
75
+ // Plain trigger while its menu is open; a caret trigger turns its caret, a filled one keeps its fill
76
+ button:not(.caret, .brand, .danger):has(+ .wu-dropdown:popover-open) {
77
+ border-color: woven.token(border-control-hover);
78
+ background: woven.token(bg-hover);
79
+ }
80
+
81
+ .wu-dropdown-item {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: woven.token(space-2);
85
+ inline-size: 100%;
86
+ min-block-size: woven.token(control-height-sm);
87
+ padding-block: 0;
88
+ padding-inline: woven.token(space-3);
89
+ border: 0;
90
+ border-radius: woven.token(radius-sm);
91
+ background: transparent;
92
+ color: inherit;
93
+ font: inherit;
94
+ text-align: start;
95
+ text-decoration: none;
96
+ white-space: nowrap;
97
+ cursor: pointer;
98
+ transition: background-color woven.token(duration-150) woven.token(ease-out);
99
+
100
+ // App icon
101
+ > svg {
102
+ flex-shrink: 0;
103
+ inline-size: 1em;
104
+ block-size: 1em;
105
+ }
106
+
107
+ &:hover {
108
+ background: woven.token(bg-hover);
109
+ }
110
+
111
+ // Inset ring, so the scrolling menu does not clip it
112
+ &:focus-visible {
113
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
114
+ outline-offset: calc(-1 * #{woven.token(border-focus-width)});
115
+ }
116
+
117
+ &:disabled,
118
+ &[aria-disabled="true"] {
119
+ opacity: woven.token(opacity-50);
120
+ cursor: not-allowed;
121
+ pointer-events: none;
122
+ }
123
+ }
124
+
125
+ .wu-dropdown-item.danger {
126
+ color: woven.token(text-danger);
127
+
128
+ &:hover {
129
+ background: woven.token(danger-subtle);
130
+ }
131
+ }
132
+
133
+ .wu-dropdown-separator {
134
+ block-size: 0;
135
+ margin-block: woven.token(space-1);
136
+ margin-inline: calc(-1 * #{woven.token(space-1)});
137
+ border: 0;
138
+ border-block-start: woven.token(border-width-1) solid woven.token(border-regular);
139
+ }
140
+
141
+ // Opt-in motion: the menu fades and slides in and out. Anchored, the slide is a margin on the trigger side,
142
+ // which flip-block mirrors, so a menu flipped above slides up; centered, it is a short translate.
143
+ @media (prefers-reduced-motion: no-preference) {
144
+ .wu-dropdown.animated,
145
+ .wu-animated .wu-dropdown {
146
+ opacity: 0;
147
+ translate: 0 calc(-1 * #{woven.token(space-1)});
148
+ transition:
149
+ opacity woven.token(duration-150) woven.token(ease-out),
150
+ translate woven.token(duration-150) woven.token(ease-out),
151
+ margin woven.token(duration-150) woven.token(ease-out),
152
+ display woven.token(duration-150) allow-discrete,
153
+ overlay woven.token(duration-150) allow-discrete;
154
+
155
+ &:popover-open {
156
+ opacity: 1;
157
+ translate: 0;
158
+
159
+ @starting-style {
160
+ opacity: 0;
161
+ translate: 0 calc(-1 * #{woven.token(space-1)});
162
+ }
163
+ }
164
+ }
165
+
166
+ @supports (anchor-name: --x) {
167
+ .wu-dropdown.animated,
168
+ .wu-animated .wu-dropdown {
169
+ translate: 0;
170
+ margin-block-start: 0;
171
+
172
+ &:popover-open {
173
+ margin-block-start: woven.token(space-1);
174
+
175
+ @starting-style {
176
+ translate: 0;
177
+ margin-block-start: 0;
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,146 @@
1
+ ////
2
+ //// @component wu-empty-state
3
+ //// Centered message for a view with no content or a finished result, with an icon, title, text and actions.
4
+ ////
5
+ //// The icon slot holds the app's own icon; left empty, it draws a calm empty-box shape. Result roles tint the slot, and an empty slot draws the alert's marker shape.
6
+ ////
7
+ //// The title text carries the meaning ("Payment received"); the shape repeats it.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <div class="wu-empty-state">
14
+ //// <span class="wu-empty-state-icon" aria-hidden="true"></span>
15
+ //// <h3 class="wu-empty-state-title">No projects yet</h3>
16
+ //// <p class="wu-empty-state-text">Create a project to get started.</p>
17
+ //// <div class="wu-empty-state-actions">
18
+ //// <button class="wu-btn brand" type="button">New project</button>
19
+ //// </div>
20
+ //// </div>
21
+ //// @example html
22
+ //// <div class="wu-empty-state success sm dashed">
23
+ //// <span class="wu-empty-state-icon" aria-hidden="true"></span>
24
+ //// <h3 class="wu-empty-state-title">Payment received</h3>
25
+ //// </div>
26
+ //// @modifier dashed - Dashed border and radius around the whole state @since 1.0.0
27
+ //// @modifier sm - Compact size for use inside a card, panel or table @since 1.0.0
28
+ //// @modifier success | warning | danger | info - Result: tints the icon slot; an empty slot draws the marker shape @since 1.0.0
29
+ //// @part wu-empty-state-icon - Round icon slot; empty, it draws a shape @since 1.0.0
30
+ //// @part wu-empty-state-title - Heading @since 1.0.0
31
+ //// @part wu-empty-state-text - Supporting text, limited to a readable width @since 1.0.0
32
+ //// @part wu-empty-state-actions - Centered row for buttons @since 1.0.0
33
+ //// @state :empty - An empty icon slot draws the empty-box or marker shape @since 1.0.0
34
+ ////
35
+
36
+ @use "@wovenui/core/mixins" as woven;
37
+
38
+ .wu-empty-state {
39
+ display: flex;
40
+ flex-direction: column;
41
+ align-items: center;
42
+ gap: woven.token(space-2);
43
+ padding-block: woven.token(space-12);
44
+ padding-inline: woven.token(space-6);
45
+ color: woven.token(text-muted);
46
+ font-size: woven.token(font-size-sm);
47
+ text-align: center;
48
+ }
49
+
50
+ .wu-empty-state-icon {
51
+ display: inline-grid;
52
+ place-items: center;
53
+ width: woven.token(space-12);
54
+ height: woven.token(space-12);
55
+ margin-block-end: woven.token(space-2);
56
+ border-radius: woven.token(radius-full);
57
+ background: woven.token(bg-alternate);
58
+ color: woven.token(text-subtle);
59
+
60
+ > * {
61
+ max-width: woven.token(space-6);
62
+ max-height: woven.token(space-6);
63
+ }
64
+
65
+ &:empty::before {
66
+ content: "";
67
+ width: woven.token(space-5);
68
+ height: woven.token(space-4);
69
+ border: woven.token(border-width-2) dashed currentcolor;
70
+ border-radius: woven.token(radius-xs);
71
+ }
72
+ }
73
+
74
+ .wu-empty-state-title {
75
+ margin: 0;
76
+ color: woven.token(text-regular);
77
+ font-size: woven.token(font-size-lg);
78
+ font-weight: woven.token(font-weight-semibold);
79
+ line-height: woven.token(line-height-tight);
80
+ }
81
+
82
+ .wu-empty-state-text {
83
+ max-inline-size: 40ch;
84
+ margin: 0;
85
+ }
86
+
87
+ .wu-empty-state-actions {
88
+ display: flex;
89
+ flex-wrap: wrap;
90
+ justify-content: center;
91
+ gap: woven.token(space-2);
92
+ margin-block-start: woven.token(space-3);
93
+ }
94
+
95
+ .wu-empty-state.dashed {
96
+ border: woven.token(border-width-1) dashed woven.token(border-strong);
97
+ border-radius: woven.token(radius-lg);
98
+ }
99
+
100
+ // Compact, for use inside a card, panel or table
101
+ .wu-empty-state.sm {
102
+ padding-block: woven.token(space-6);
103
+ padding-inline: woven.token(space-4);
104
+
105
+ .wu-empty-state-title {
106
+ font-size: woven.token(font-size-md);
107
+ }
108
+
109
+ .wu-empty-state-icon {
110
+ width: woven.token(space-10);
111
+ height: woven.token(space-10);
112
+ margin-block-end: 0;
113
+ }
114
+ }
115
+
116
+ // Result variants with the shared marker shapes from _shared.scss
117
+ @each $role in "success", "warning", "danger", "info" {
118
+ .wu-empty-state.#{$role} .wu-empty-state-icon {
119
+ background: woven.token(#{$role}-subtle);
120
+ color: woven.token(text-#{$role});
121
+
122
+ &:empty::before {
123
+ width: woven.token(space-6);
124
+ height: woven.token(space-6);
125
+ border: 0;
126
+ border-radius: woven.token(radius-full);
127
+ background: currentcolor;
128
+ }
129
+ }
130
+ }
131
+
132
+ .wu-empty-state:is(.success, .warning, .danger) .wu-empty-state-icon:empty::before {
133
+ border-radius: 0;
134
+ }
135
+
136
+ .wu-empty-state.success .wu-empty-state-icon:empty::before {
137
+ clip-path: var(--wu-kit-marker-check);
138
+ }
139
+
140
+ .wu-empty-state.warning .wu-empty-state-icon:empty::before {
141
+ clip-path: var(--wu-kit-marker-triangle);
142
+ }
143
+
144
+ .wu-empty-state.danger .wu-empty-state-icon:empty::before {
145
+ clip-path: var(--wu-kit-marker-octagon);
146
+ }