luxen-ui 0.16.2 → 0.17.1
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.
- package/README.md +50 -49
- package/cdn/custom-elements.json +237 -1
- package/cdn/elements/avatar/avatar.d.ts.map +1 -1
- package/cdn/elements/avatar/avatar.js +1 -1
- package/cdn/elements/avatar/avatar.js.map +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.d.ts +4 -0
- package/cdn/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js +2 -2
- package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
- package/cdn/elements/segmented-control/index.d.ts +2 -0
- package/cdn/elements/segmented-control/index.d.ts.map +1 -0
- package/cdn/elements/segmented-control/index.js +2 -0
- package/cdn/elements/segmented-control/index.js.map +1 -0
- package/cdn/elements/segmented-control/segmented-control.d.ts +80 -0
- package/cdn/elements/segmented-control/segmented-control.d.ts.map +1 -0
- package/cdn/elements/segmented-control/segmented-control.js +2 -0
- package/cdn/elements/segmented-control/segmented-control.js.map +1 -0
- package/cdn/registry.d.ts +1 -1
- package/cdn/registry.d.ts.map +1 -1
- package/cdn/registry.js.map +1 -1
- package/cdn/standalone.css +167 -0
- package/cdn/standalone.js +25 -22
- package/cdn/standalone.js.map +1 -1
- package/cdn/styles/elements/segmented-control.css +165 -0
- package/dist/css/elements/segmented-control.css +165 -0
- package/dist/custom-elements.json +237 -1
- package/dist/elements/avatar/avatar.d.ts.map +1 -1
- package/dist/elements/avatar/avatar.js +27 -7
- package/dist/elements/dropdown-item/dropdown-item.css +18 -0
- package/dist/elements/dropdown-item/dropdown-item.d.ts +4 -0
- package/dist/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
- package/dist/elements/dropdown-item/dropdown-item.js +11 -2
- package/dist/elements/segmented-control/index.d.ts +2 -0
- package/dist/elements/segmented-control/index.d.ts.map +1 -0
- package/dist/elements/segmented-control/index.js +4 -0
- package/dist/elements/segmented-control/segmented-control.d.ts +80 -0
- package/dist/elements/segmented-control/segmented-control.d.ts.map +1 -0
- package/dist/elements/segmented-control/segmented-control.js +307 -0
- package/dist/metadata/dropdown-item.json +16 -1
- package/dist/metadata/index.json +122 -2
- package/dist/metadata/segmented-control.json +105 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/templates/elements/dropdown.md +52 -0
- package/dist/templates/elements/segmented-control.md +252 -0
- package/elements.json +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/*
|
|
3
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
4
|
+
Segmented control
|
|
5
|
+
A single-select switch: a muted track with a sliding pill behind the
|
|
6
|
+
selected segment. Shares its visual language with l-tabs[variant='enclosed']
|
|
7
|
+
(same neutral-subtle track + surface pill), but carries radio-group
|
|
8
|
+
semantics instead of tabs.
|
|
9
|
+
|
|
10
|
+
Structural selectors (not [role='…']) so the control is styled from plain
|
|
11
|
+
light DOM — it is progressive, so it must look right before JS adds ARIA.
|
|
12
|
+
The host is the flex track; its `<button>` children are the segments. JS
|
|
13
|
+
only enhances (roles, keyboard, and the sliding pill offsets).
|
|
14
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
l-segmented-control {
|
|
18
|
+
--_height: var(--l-size-control-md);
|
|
19
|
+
--_pad: 0.1875rem;
|
|
20
|
+
--_duration: 150ms;
|
|
21
|
+
--_easing: cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
22
|
+
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
position: relative;
|
|
25
|
+
align-items: stretch;
|
|
26
|
+
height: var(--_height);
|
|
27
|
+
padding: var(--_pad);
|
|
28
|
+
gap: calc(var(--spacing) * 0.5);
|
|
29
|
+
background: var(--l-color-bg-fill-neutral-subtle);
|
|
30
|
+
border-radius: var(--border-radius, var(--l-radius-lg));
|
|
31
|
+
/* Match the form-control border (--l-color-border, as used by .l-button,
|
|
32
|
+
l-select, and l-input) so the track lines up with dropdowns in a toolbar
|
|
33
|
+
— not the lighter --l-color-divider that enclosed tabs use. */
|
|
34
|
+
box-shadow: inset 0 0 0 1px var(--l-color-border);
|
|
35
|
+
|
|
36
|
+
/* Sliding pill behind the selected segment. Positioned by JS via
|
|
37
|
+
--_indicator-left / --_indicator-width (button offsetLeft / offsetWidth);
|
|
38
|
+
width 0 until JS runs, so it stays invisible in the no-JS fallback. */
|
|
39
|
+
&::before {
|
|
40
|
+
content: '';
|
|
41
|
+
position: absolute;
|
|
42
|
+
top: var(--_pad);
|
|
43
|
+
bottom: var(--_pad);
|
|
44
|
+
left: 0;
|
|
45
|
+
width: var(--_indicator-width, 0);
|
|
46
|
+
transform: translateX(var(--_indicator-left, 0));
|
|
47
|
+
background: var(--indicator-color, var(--l-color-surface));
|
|
48
|
+
border-radius: var(--l-radius-md);
|
|
49
|
+
box-shadow:
|
|
50
|
+
0 1px 3px 0 rgb(0 0 0 / 0.08),
|
|
51
|
+
0 1px 2px -1px rgb(0 0 0 / 0.08);
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
will-change: transform, width;
|
|
54
|
+
transition:
|
|
55
|
+
transform var(--_duration) var(--_easing),
|
|
56
|
+
width var(--_duration) var(--_easing);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
62
|
+
Sizes — align with .l-button and form controls at the same size.
|
|
63
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
l-segmented-control[size='sm'] {
|
|
67
|
+
--_height: var(--l-size-control-sm);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
l-segmented-control[size='lg'] {
|
|
71
|
+
--_height: var(--l-size-control-lg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
l-segmented-control[size='xl'] {
|
|
75
|
+
--_height: var(--l-size-control-xl);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Whole-control disabled (host `disabled` attribute, form-associated). */
|
|
79
|
+
l-segmented-control[disabled] {
|
|
80
|
+
opacity: 0.5;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
86
|
+
Segments
|
|
87
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
l-segmented-control button {
|
|
91
|
+
position: relative;
|
|
92
|
+
z-index: 1;
|
|
93
|
+
display: inline-flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
gap: calc(var(--spacing) * 1.5);
|
|
97
|
+
padding-inline: calc(var(--spacing) * 3);
|
|
98
|
+
margin: 0;
|
|
99
|
+
appearance: none;
|
|
100
|
+
border: none;
|
|
101
|
+
background: none;
|
|
102
|
+
border-radius: var(--l-radius-md);
|
|
103
|
+
color: var(--l-color-text-secondary);
|
|
104
|
+
font-family: inherit;
|
|
105
|
+
font-size: var(--l-text-sm);
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
-webkit-tap-highlight-color: transparent;
|
|
111
|
+
transition: color 150ms;
|
|
112
|
+
|
|
113
|
+
/* Icon-only segments (JS-detected, no visible text): square them. */
|
|
114
|
+
&[data-icon-only] {
|
|
115
|
+
padding-inline: 0;
|
|
116
|
+
width: calc(var(--_height) - 2 * var(--_pad));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&[aria-checked='true'] {
|
|
120
|
+
color: var(--l-color-text-primary);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (hover: hover) {
|
|
124
|
+
&:hover:not([aria-checked='true']):not(:disabled):not([aria-disabled='true']) {
|
|
125
|
+
color: var(--l-color-text-primary);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
&:disabled,
|
|
130
|
+
&[aria-disabled='true'] {
|
|
131
|
+
cursor: not-allowed;
|
|
132
|
+
opacity: 0.4;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&:focus-visible {
|
|
136
|
+
outline: 2px solid var(--l-focus-ring);
|
|
137
|
+
outline-offset: -2px;
|
|
138
|
+
border-radius: var(--l-radius-sm);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/*
|
|
143
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
144
|
+
Full width
|
|
145
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
l-segmented-control[full-width] {
|
|
149
|
+
display: flex;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
l-segmented-control[full-width] button {
|
|
153
|
+
flex: 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@media (prefers-reduced-motion: reduce) {
|
|
157
|
+
l-segmented-control::before {
|
|
158
|
+
transition-duration: 0ms;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
l-segmented-control button {
|
|
162
|
+
transition-duration: 0ms;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/*
|
|
3
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
4
|
+
Segmented control
|
|
5
|
+
A single-select switch: a muted track with a sliding pill behind the
|
|
6
|
+
selected segment. Shares its visual language with l-tabs[variant='enclosed']
|
|
7
|
+
(same neutral-subtle track + surface pill), but carries radio-group
|
|
8
|
+
semantics instead of tabs.
|
|
9
|
+
|
|
10
|
+
Structural selectors (not [role='…']) so the control is styled from plain
|
|
11
|
+
light DOM — it is progressive, so it must look right before JS adds ARIA.
|
|
12
|
+
The host is the flex track; its `<button>` children are the segments. JS
|
|
13
|
+
only enhances (roles, keyboard, and the sliding pill offsets).
|
|
14
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
l-segmented-control {
|
|
18
|
+
--_height: var(--l-size-control-md);
|
|
19
|
+
--_pad: 0.1875rem;
|
|
20
|
+
--_duration: 150ms;
|
|
21
|
+
--_easing: cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
22
|
+
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
position: relative;
|
|
25
|
+
align-items: stretch;
|
|
26
|
+
height: var(--_height);
|
|
27
|
+
padding: var(--_pad);
|
|
28
|
+
gap: calc(var(--spacing) * 0.5);
|
|
29
|
+
background: var(--l-color-bg-fill-neutral-subtle);
|
|
30
|
+
border-radius: var(--border-radius, var(--l-radius-lg));
|
|
31
|
+
/* Match the form-control border (--l-color-border, as used by .l-button,
|
|
32
|
+
l-select, and l-input) so the track lines up with dropdowns in a toolbar
|
|
33
|
+
— not the lighter --l-color-divider that enclosed tabs use. */
|
|
34
|
+
box-shadow: inset 0 0 0 1px var(--l-color-border);
|
|
35
|
+
|
|
36
|
+
/* Sliding pill behind the selected segment. Positioned by JS via
|
|
37
|
+
--_indicator-left / --_indicator-width (button offsetLeft / offsetWidth);
|
|
38
|
+
width 0 until JS runs, so it stays invisible in the no-JS fallback. */
|
|
39
|
+
&::before {
|
|
40
|
+
content: '';
|
|
41
|
+
position: absolute;
|
|
42
|
+
top: var(--_pad);
|
|
43
|
+
bottom: var(--_pad);
|
|
44
|
+
left: 0;
|
|
45
|
+
width: var(--_indicator-width, 0);
|
|
46
|
+
transform: translateX(var(--_indicator-left, 0));
|
|
47
|
+
background: var(--indicator-color, var(--l-color-surface));
|
|
48
|
+
border-radius: var(--l-radius-md);
|
|
49
|
+
box-shadow:
|
|
50
|
+
0 1px 3px 0 rgb(0 0 0 / 0.08),
|
|
51
|
+
0 1px 2px -1px rgb(0 0 0 / 0.08);
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
will-change: transform, width;
|
|
54
|
+
transition:
|
|
55
|
+
transform var(--_duration) var(--_easing),
|
|
56
|
+
width var(--_duration) var(--_easing);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
62
|
+
Sizes — align with .l-button and form controls at the same size.
|
|
63
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
l-segmented-control[size='sm'] {
|
|
67
|
+
--_height: var(--l-size-control-sm);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
l-segmented-control[size='lg'] {
|
|
71
|
+
--_height: var(--l-size-control-lg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
l-segmented-control[size='xl'] {
|
|
75
|
+
--_height: var(--l-size-control-xl);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Whole-control disabled (host `disabled` attribute, form-associated). */
|
|
79
|
+
l-segmented-control[disabled] {
|
|
80
|
+
opacity: 0.5;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
86
|
+
Segments
|
|
87
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
l-segmented-control button {
|
|
91
|
+
position: relative;
|
|
92
|
+
z-index: 1;
|
|
93
|
+
display: inline-flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
gap: calc(var(--spacing) * 1.5);
|
|
97
|
+
padding-inline: calc(var(--spacing) * 3);
|
|
98
|
+
margin: 0;
|
|
99
|
+
appearance: none;
|
|
100
|
+
border: none;
|
|
101
|
+
background: none;
|
|
102
|
+
border-radius: var(--l-radius-md);
|
|
103
|
+
color: var(--l-color-text-secondary);
|
|
104
|
+
font-family: inherit;
|
|
105
|
+
font-size: var(--l-text-sm);
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
-webkit-tap-highlight-color: transparent;
|
|
111
|
+
transition: color 150ms;
|
|
112
|
+
|
|
113
|
+
/* Icon-only segments (JS-detected, no visible text): square them. */
|
|
114
|
+
&[data-icon-only] {
|
|
115
|
+
padding-inline: 0;
|
|
116
|
+
width: calc(var(--_height) - 2 * var(--_pad));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&[aria-checked='true'] {
|
|
120
|
+
color: var(--l-color-text-primary);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (hover: hover) {
|
|
124
|
+
&:hover:not([aria-checked='true']):not(:disabled):not([aria-disabled='true']) {
|
|
125
|
+
color: var(--l-color-text-primary);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
&:disabled,
|
|
130
|
+
&[aria-disabled='true'] {
|
|
131
|
+
cursor: not-allowed;
|
|
132
|
+
opacity: 0.4;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&:focus-visible {
|
|
136
|
+
outline: 2px solid var(--l-focus-ring);
|
|
137
|
+
outline-offset: -2px;
|
|
138
|
+
border-radius: var(--l-radius-sm);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/*
|
|
143
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
144
|
+
Full width
|
|
145
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
l-segmented-control[full-width] {
|
|
149
|
+
display: flex;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
l-segmented-control[full-width] button {
|
|
153
|
+
flex: 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@media (prefers-reduced-motion: reduce) {
|
|
157
|
+
l-segmented-control::before {
|
|
158
|
+
transition-duration: 0ms;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
l-segmented-control button {
|
|
162
|
+
transition-duration: 0ms;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -3750,6 +3750,17 @@
|
|
|
3750
3750
|
"description": "The type of item: `normal` or `checkbox`.",
|
|
3751
3751
|
"attribute": "type"
|
|
3752
3752
|
},
|
|
3753
|
+
{
|
|
3754
|
+
"kind": "field",
|
|
3755
|
+
"name": "variant",
|
|
3756
|
+
"type": {
|
|
3757
|
+
"text": "'destructive' | undefined"
|
|
3758
|
+
},
|
|
3759
|
+
"default": "undefined",
|
|
3760
|
+
"description": "Visual variant. `destructive` marks a delete-style action in red.",
|
|
3761
|
+
"attribute": "variant",
|
|
3762
|
+
"reflects": true
|
|
3763
|
+
},
|
|
3753
3764
|
{
|
|
3754
3765
|
"kind": "field",
|
|
3755
3766
|
"name": "checked",
|
|
@@ -3813,6 +3824,15 @@
|
|
|
3813
3824
|
}
|
|
3814
3825
|
],
|
|
3815
3826
|
"attributes": [
|
|
3827
|
+
{
|
|
3828
|
+
"description": "Visual variant. `destructive` marks a delete-style action in red.",
|
|
3829
|
+
"name": "variant",
|
|
3830
|
+
"type": {
|
|
3831
|
+
"text": "'destructive' | undefined"
|
|
3832
|
+
},
|
|
3833
|
+
"default": "undefined",
|
|
3834
|
+
"fieldName": "variant"
|
|
3835
|
+
},
|
|
3816
3836
|
{
|
|
3817
3837
|
"name": "value",
|
|
3818
3838
|
"type": {
|
|
@@ -3864,7 +3884,13 @@
|
|
|
3864
3884
|
"module": "/src/html/shared/luxen-element.js"
|
|
3865
3885
|
},
|
|
3866
3886
|
"tagName": "l-dropdown-item",
|
|
3867
|
-
"customElement": true
|
|
3887
|
+
"customElement": true,
|
|
3888
|
+
"_attributes": [
|
|
3889
|
+
{
|
|
3890
|
+
"name": "variant",
|
|
3891
|
+
"description": "destructive — Renders the item as a destructive action (red), for delete-style entries."
|
|
3892
|
+
}
|
|
3893
|
+
]
|
|
3868
3894
|
}
|
|
3869
3895
|
],
|
|
3870
3896
|
"exports": [
|
|
@@ -6041,6 +6067,216 @@
|
|
|
6041
6067
|
}
|
|
6042
6068
|
]
|
|
6043
6069
|
},
|
|
6070
|
+
{
|
|
6071
|
+
"kind": "javascript-module",
|
|
6072
|
+
"path": "src/html/elements/segmented-control/index.ts",
|
|
6073
|
+
"declarations": [],
|
|
6074
|
+
"exports": [
|
|
6075
|
+
{
|
|
6076
|
+
"kind": "js",
|
|
6077
|
+
"name": "*",
|
|
6078
|
+
"declaration": {
|
|
6079
|
+
"name": "*",
|
|
6080
|
+
"module": "src/html/elements/segmented-control/segmented-control.js"
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
]
|
|
6084
|
+
},
|
|
6085
|
+
{
|
|
6086
|
+
"kind": "javascript-module",
|
|
6087
|
+
"path": "src/html/elements/segmented-control/segmented-control.ts",
|
|
6088
|
+
"declarations": [
|
|
6089
|
+
{
|
|
6090
|
+
"kind": "class",
|
|
6091
|
+
"description": "Fired when the selected segment changes. Bubbles; not composed.",
|
|
6092
|
+
"name": "SegmentChangeEvent",
|
|
6093
|
+
"members": [
|
|
6094
|
+
{
|
|
6095
|
+
"kind": "field",
|
|
6096
|
+
"name": "value",
|
|
6097
|
+
"type": {
|
|
6098
|
+
"text": "string"
|
|
6099
|
+
},
|
|
6100
|
+
"readonly": true,
|
|
6101
|
+
"default": "value"
|
|
6102
|
+
},
|
|
6103
|
+
{
|
|
6104
|
+
"kind": "field",
|
|
6105
|
+
"name": "index",
|
|
6106
|
+
"type": {
|
|
6107
|
+
"text": "number"
|
|
6108
|
+
},
|
|
6109
|
+
"readonly": true,
|
|
6110
|
+
"default": "index"
|
|
6111
|
+
}
|
|
6112
|
+
],
|
|
6113
|
+
"superclass": {
|
|
6114
|
+
"name": "Event",
|
|
6115
|
+
"module": "src/html/elements/segmented-control/segmented-control.ts"
|
|
6116
|
+
}
|
|
6117
|
+
},
|
|
6118
|
+
{
|
|
6119
|
+
"kind": "class",
|
|
6120
|
+
"description": "",
|
|
6121
|
+
"name": "SegmentedControl",
|
|
6122
|
+
"cssProperties": [
|
|
6123
|
+
{
|
|
6124
|
+
"description": "Background of the sliding pill behind the selected segment.",
|
|
6125
|
+
"name": "--indicator-color",
|
|
6126
|
+
"default": "var(--l-color-surface)"
|
|
6127
|
+
},
|
|
6128
|
+
{
|
|
6129
|
+
"description": "Corner radius of the track.",
|
|
6130
|
+
"name": "--border-radius",
|
|
6131
|
+
"default": "var(--l-radius-lg)"
|
|
6132
|
+
}
|
|
6133
|
+
],
|
|
6134
|
+
"members": [
|
|
6135
|
+
{
|
|
6136
|
+
"kind": "field",
|
|
6137
|
+
"name": "value",
|
|
6138
|
+
"type": {
|
|
6139
|
+
"text": "string"
|
|
6140
|
+
},
|
|
6141
|
+
"default": "''",
|
|
6142
|
+
"description": "Value of the selected segment (its `value` attribute, else its index).",
|
|
6143
|
+
"attribute": "value",
|
|
6144
|
+
"reflects": true
|
|
6145
|
+
},
|
|
6146
|
+
{
|
|
6147
|
+
"kind": "field",
|
|
6148
|
+
"name": "size",
|
|
6149
|
+
"type": {
|
|
6150
|
+
"text": "SegmentedControlSize"
|
|
6151
|
+
},
|
|
6152
|
+
"default": "'md'",
|
|
6153
|
+
"description": "Control height, aligned with buttons and form controls at the same size.",
|
|
6154
|
+
"attribute": "size",
|
|
6155
|
+
"reflects": true
|
|
6156
|
+
},
|
|
6157
|
+
{
|
|
6158
|
+
"kind": "field",
|
|
6159
|
+
"name": "label",
|
|
6160
|
+
"type": {
|
|
6161
|
+
"text": "string | undefined"
|
|
6162
|
+
},
|
|
6163
|
+
"description": "Accessible label announced for the group. Not displayed on screen.",
|
|
6164
|
+
"attribute": "label",
|
|
6165
|
+
"reflects": true
|
|
6166
|
+
},
|
|
6167
|
+
{
|
|
6168
|
+
"kind": "field",
|
|
6169
|
+
"name": "fullWidth",
|
|
6170
|
+
"type": {
|
|
6171
|
+
"text": "boolean"
|
|
6172
|
+
},
|
|
6173
|
+
"default": "false",
|
|
6174
|
+
"description": "Stretch segments to fill the container width.",
|
|
6175
|
+
"attribute": "full-width",
|
|
6176
|
+
"reflects": true
|
|
6177
|
+
},
|
|
6178
|
+
{
|
|
6179
|
+
"kind": "method",
|
|
6180
|
+
"name": "formResetCallback"
|
|
6181
|
+
},
|
|
6182
|
+
{
|
|
6183
|
+
"kind": "method",
|
|
6184
|
+
"name": "formStateRestoreCallback",
|
|
6185
|
+
"parameters": [
|
|
6186
|
+
{
|
|
6187
|
+
"name": "state",
|
|
6188
|
+
"type": {
|
|
6189
|
+
"text": "string"
|
|
6190
|
+
}
|
|
6191
|
+
},
|
|
6192
|
+
{
|
|
6193
|
+
"name": "mode",
|
|
6194
|
+
"type": {
|
|
6195
|
+
"text": "'restore' | 'autocomplete'"
|
|
6196
|
+
}
|
|
6197
|
+
}
|
|
6198
|
+
]
|
|
6199
|
+
}
|
|
6200
|
+
],
|
|
6201
|
+
"events": [
|
|
6202
|
+
{
|
|
6203
|
+
"name": "change",
|
|
6204
|
+
"description": "Fired when the selected segment changes. Not cancelable. Bubbles. Properties: `value: string`, `index: number`.",
|
|
6205
|
+
"cancelable": false
|
|
6206
|
+
}
|
|
6207
|
+
],
|
|
6208
|
+
"attributes": [
|
|
6209
|
+
{
|
|
6210
|
+
"name": "value",
|
|
6211
|
+
"type": {
|
|
6212
|
+
"text": "string"
|
|
6213
|
+
},
|
|
6214
|
+
"default": "''",
|
|
6215
|
+
"description": "Value of the selected segment (its `value` attribute, else its index).",
|
|
6216
|
+
"fieldName": "value"
|
|
6217
|
+
},
|
|
6218
|
+
{
|
|
6219
|
+
"name": "size",
|
|
6220
|
+
"type": {
|
|
6221
|
+
"text": "SegmentedControlSize"
|
|
6222
|
+
},
|
|
6223
|
+
"default": "'md'",
|
|
6224
|
+
"description": "Control height, aligned with buttons and form controls at the same size.",
|
|
6225
|
+
"fieldName": "size"
|
|
6226
|
+
},
|
|
6227
|
+
{
|
|
6228
|
+
"name": "label",
|
|
6229
|
+
"type": {
|
|
6230
|
+
"text": "string | undefined"
|
|
6231
|
+
},
|
|
6232
|
+
"description": "Accessible label announced for the group. Not displayed on screen.",
|
|
6233
|
+
"fieldName": "label"
|
|
6234
|
+
},
|
|
6235
|
+
{
|
|
6236
|
+
"name": "full-width",
|
|
6237
|
+
"type": {
|
|
6238
|
+
"text": "boolean"
|
|
6239
|
+
},
|
|
6240
|
+
"default": "false",
|
|
6241
|
+
"description": "Stretch segments to fill the container width.",
|
|
6242
|
+
"fieldName": "fullWidth"
|
|
6243
|
+
}
|
|
6244
|
+
],
|
|
6245
|
+
"superclass": {
|
|
6246
|
+
"name": "LuxenFormAssociatedElement",
|
|
6247
|
+
"module": "/src/html/shared/luxen-form-associated-element.js"
|
|
6248
|
+
},
|
|
6249
|
+
"tagName": "l-segmented-control",
|
|
6250
|
+
"customElement": true,
|
|
6251
|
+
"summary": "A single-select segmented control: a compact, mutually-exclusive\nswitch between a few options (radio-group semantics with a sliding pill).\nProgressively enhances light-DOM `<button>`s so it works before JS runs.\nForm-associated: give it a `name` and its selected `value` is submitted with\nthe form (and restored on reset), like a native radio group.",
|
|
6252
|
+
"examples": [
|
|
6253
|
+
{
|
|
6254
|
+
"title": "Mark the initially-selected segment with `aria-checked=\"true\"` (or set",
|
|
6255
|
+
"language": "html",
|
|
6256
|
+
"code": "`value` to its `value` attribute) so it is styled before the script upgrades\nthe element.\n```html\n<l-segmented-control label=\"Metric\" value=\"cost\">\n <button value=\"volume\">Volume</button>\n <button value=\"cost\" aria-checked=\"true\">Cost</button>\n</l-segmented-control>\n```"
|
|
6257
|
+
}
|
|
6258
|
+
]
|
|
6259
|
+
}
|
|
6260
|
+
],
|
|
6261
|
+
"exports": [
|
|
6262
|
+
{
|
|
6263
|
+
"kind": "js",
|
|
6264
|
+
"name": "SegmentChangeEvent",
|
|
6265
|
+
"declaration": {
|
|
6266
|
+
"name": "SegmentChangeEvent",
|
|
6267
|
+
"module": "src/html/elements/segmented-control/segmented-control.ts"
|
|
6268
|
+
}
|
|
6269
|
+
},
|
|
6270
|
+
{
|
|
6271
|
+
"kind": "js",
|
|
6272
|
+
"name": "SegmentedControl",
|
|
6273
|
+
"declaration": {
|
|
6274
|
+
"name": "SegmentedControl",
|
|
6275
|
+
"module": "src/html/elements/segmented-control/segmented-control.ts"
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
]
|
|
6279
|
+
},
|
|
6044
6280
|
{
|
|
6045
6281
|
"kind": "javascript-module",
|
|
6046
6282
|
"path": "src/html/elements/select/index.ts",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../../src/html/elements/avatar/avatar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../../src/html/elements/avatar/avatar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA0C7D;;;;;;;;;;;;GAYG;AACH,qBAAa,MAAO,SAAQ,YAAY;IACtC,MAAM,CAAC,MAAM,4BAAwB;IAErC,qFAAqF;IAErF,GAAG,SAAM;IAET,6EAA6E;IAE7E,IAAI,SAAM;IAEV,8DAA8D;IAE9D,IAAI,SAAQ;IAEZ,4DAA4D;IAE5D,KAAK,SAAK;IAEV,+DAA+D;IAE/D,WAAW,UAAS;IAEX,OAAO,CAAC,SAAS,CAAS;IAEnC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAMxC,iBAAiB;IAOjB,OAAO;IAgBP,OAAO,KAAK,IAAI,GAEf;IAED,MAAM;IAkCN,OAAO,CAAC,QAAQ,CAEd;CACH"}
|
|
@@ -11,13 +11,33 @@ import { property, state } from 'lit/decorators.js';
|
|
|
11
11
|
import hostStyles from '../../shared/styles/host.styles.js';
|
|
12
12
|
import rawStyles from './avatar.css?inline';
|
|
13
13
|
const styles = unsafeCSS(rawStyles);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Extracts up to two uppercase initials from a name: the first letter of the
|
|
16
|
+
* first word, plus the first letter of the last word when there is more than one.
|
|
17
|
+
*
|
|
18
|
+
* Splits on whitespace rather than relying on a `\b`/`\w` regex, which only
|
|
19
|
+
* recognises ASCII and mangles accented or non-ASCII names.
|
|
20
|
+
*
|
|
21
|
+
* @param name - The name to extract initials from. Defaults to an empty string.
|
|
22
|
+
* @return The uppercased initials, or an empty string when `name` is blank.
|
|
23
|
+
*
|
|
24
|
+
* @example getInitials('John Doe') // 'JD'
|
|
25
|
+
* @example getInitials('Markus Nösterer') // 'MN' (accents preserved)
|
|
26
|
+
* @example getInitials('Cher') // 'C' (single word → one initial)
|
|
27
|
+
* @example getInitials(' John Doe ') // 'JD' (extra whitespace ignored)
|
|
28
|
+
* @example getInitials('') // ''
|
|
29
|
+
*/
|
|
30
|
+
function getInitials(name = '') {
|
|
31
|
+
const words = name.normalize('NFC').trim().split(/\s+/).filter(Boolean);
|
|
32
|
+
if (words.length === 0)
|
|
33
|
+
return '';
|
|
34
|
+
// Spreading iterates by code point, so an accented letter (or an astral
|
|
35
|
+
// character like an emoji) is taken whole instead of a broken half.
|
|
36
|
+
// oxlint-disable-next-line typescript/no-misused-spread -- code-point-safe first char is the intent
|
|
37
|
+
const firstLetter = (word) => [...word][0];
|
|
38
|
+
const first = firstLetter(words[0]);
|
|
39
|
+
const last = words.length > 1 ? firstLetter(words[words.length - 1]) : '';
|
|
40
|
+
return (first + last).toUpperCase();
|
|
21
41
|
}
|
|
22
42
|
const defaultIcon = svg `<svg class="icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
23
43
|
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
|
|
@@ -44,6 +44,24 @@
|
|
|
44
44
|
background: var(--l-color-bg-state-hover);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/* Destructive variant — a delete-style action. Red label at rest, danger-tinted
|
|
48
|
+
fill and focus ring on hover/focus so the highlight reads as "danger" rather
|
|
49
|
+
than the neutral hover tint. */
|
|
50
|
+
:host([variant='destructive']) .item {
|
|
51
|
+
color: var(--l-color-text-danger);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:host([variant='destructive']) .item:focus-visible {
|
|
55
|
+
background: var(--l-color-bg-fill-danger-soft);
|
|
56
|
+
outline-color: var(--l-color-text-danger);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (hover: hover) {
|
|
60
|
+
:host([variant='destructive']) .item:hover {
|
|
61
|
+
background: var(--l-color-bg-fill-danger-soft);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
.check {
|
|
48
66
|
display: flex;
|
|
49
67
|
width: 16px;
|
|
@@ -12,6 +12,8 @@ import { LuxenElement } from '../../shared/luxen-element.js';
|
|
|
12
12
|
*
|
|
13
13
|
* @cssproperty --color - Text color.
|
|
14
14
|
*
|
|
15
|
+
* @attribute variant - destructive — Renders the item as a destructive action (red), for delete-style entries.
|
|
16
|
+
*
|
|
15
17
|
* @customElement l-dropdown-item
|
|
16
18
|
*/
|
|
17
19
|
export declare class DropdownItem extends LuxenElement {
|
|
@@ -23,6 +25,8 @@ export declare class DropdownItem extends LuxenElement {
|
|
|
23
25
|
accessor disabled: boolean;
|
|
24
26
|
/** The type of item: `normal` or `checkbox`. */
|
|
25
27
|
accessor type: 'normal' | 'checkbox';
|
|
28
|
+
/** Visual variant. `destructive` marks a delete-style action in red. */
|
|
29
|
+
accessor variant: 'destructive' | undefined;
|
|
26
30
|
/** Whether the checkbox item is checked. */
|
|
27
31
|
accessor checked: boolean;
|
|
28
32
|
/** Whether this item's submenu is open. Managed by the parent `l-dropdown`. */
|