@timmo001/groundwork-ui 0.6.0 → 0.7.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.
- package/custom-elements.json +214 -18
- package/package.json +1 -1
- package/src/fields.ts +32 -6
- package/src/gw-badge.ts +29 -18
- package/src/gw-button.ts +8 -24
- package/src/gw-card.ts +97 -4
- package/src/gw-chip.ts +26 -7
- package/src/gw-input.ts +7 -0
- package/src/gw-menu-button.ts +17 -5
- package/src/gw-segmented.ts +19 -7
- package/src/gw-select.ts +7 -0
- package/src/gw-setting-row.ts +21 -5
- package/src/styles.ts +25 -0
package/src/fields.ts
CHANGED
|
@@ -70,12 +70,37 @@ export const fieldStyles = css`
|
|
|
70
70
|
fill: currentColor;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/* Sizes run xs to xl through --gw-field-scale, around 1 for medium, and
|
|
74
|
+
the control's height follows the theme's density too */
|
|
75
|
+
:where(gw-input, gw-select)[size="xs"] {
|
|
76
|
+
--gw-field-scale: 0.6;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
:where(gw-input, gw-select)[size="small"] {
|
|
80
|
+
--gw-field-scale: 0.8;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:where(gw-input, gw-select)[size="large"] {
|
|
84
|
+
--gw-field-scale: 1.2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:where(gw-input, gw-select)[size="xl"] {
|
|
88
|
+
--gw-field-scale: 1.4;
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
:where(gw-input, gw-select) :where(input, .gw-select-trigger) {
|
|
92
|
+
--gw-field-size: var(--gw-field-scale, 1);
|
|
93
|
+
--gw-icon-size: calc(
|
|
94
|
+
1.25rem * var(--gw-field-size) * var(--gw-control-scale, 1)
|
|
95
|
+
);
|
|
96
|
+
|
|
74
97
|
width: 100%;
|
|
75
|
-
min-height:
|
|
76
|
-
|
|
98
|
+
min-height: calc(
|
|
99
|
+
2.5rem * var(--gw-field-size) * var(--gw-control-scale, 1)
|
|
100
|
+
);
|
|
101
|
+
padding: 0 calc(var(--gw-space-3) * var(--gw-field-size));
|
|
77
102
|
font: inherit;
|
|
78
|
-
font-size: var(--gw-font-size-s);
|
|
103
|
+
font-size: calc(var(--gw-font-size-s) * var(--gw-field-size));
|
|
79
104
|
color: var(--gw-color-text-strong);
|
|
80
105
|
background: var(--gw-color-surface-lower);
|
|
81
106
|
border: var(--gw-border-width) solid var(--gw-color-border-normal);
|
|
@@ -87,9 +112,9 @@ export const fieldStyles = css`
|
|
|
87
112
|
room around it that turns over while the list is open */
|
|
88
113
|
gw-select .gw-select-trigger {
|
|
89
114
|
display: flex;
|
|
90
|
-
gap: var(--gw-space-2);
|
|
115
|
+
gap: calc(var(--gw-space-2) * var(--gw-field-size));
|
|
91
116
|
align-items: center;
|
|
92
|
-
padding-inline-end: var(--gw-space-3);
|
|
117
|
+
padding-inline-end: calc(var(--gw-space-3) * var(--gw-field-size));
|
|
93
118
|
text-align: start;
|
|
94
119
|
cursor: pointer;
|
|
95
120
|
}
|
|
@@ -110,7 +135,8 @@ export const fieldStyles = css`
|
|
|
110
135
|
|
|
111
136
|
gw-select[inline-label] .gw-select-trigger {
|
|
112
137
|
width: auto;
|
|
113
|
-
padding-inline: var(--gw-space-4) var(--gw-
|
|
138
|
+
padding-inline: calc(var(--gw-space-4) * var(--gw-field-size))
|
|
139
|
+
calc(var(--gw-space-3) * var(--gw-field-size));
|
|
114
140
|
border-radius: var(--gw-radius-pill);
|
|
115
141
|
}
|
|
116
142
|
|
package/src/gw-badge.ts
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
import { GwElement, css, h, setAttribute } from "./element.js";
|
|
8
8
|
import "./gw-skeleton.js";
|
|
9
9
|
import { fadeIn } from "./motion.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
base,
|
|
12
|
+
controlSizes,
|
|
13
|
+
createIcon,
|
|
14
|
+
setIconPath,
|
|
15
|
+
type Tone,
|
|
16
|
+
} from "./styles.js";
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* A pill with an icon, a value and an optional small label above it. The
|
|
@@ -21,7 +27,7 @@ import { base, createIcon, setIconPath, type Tone } from "./styles.js";
|
|
|
21
27
|
* @attr {string} icon - SVG path data for the icon.
|
|
22
28
|
* @attr {string} label - A small label above the value.
|
|
23
29
|
* @attr {"neutral"|"accent"|"info"|"success"|"warning"|"danger"|"important"} tone - The icon's color. Defaults to `accent`.
|
|
24
|
-
* @attr {"small"|"medium"} size - The size. Defaults to `medium`.
|
|
30
|
+
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The size, around 36px tall at medium. Defaults to `medium`.
|
|
25
31
|
* @attr {boolean} loading - Shows a skeleton for the value, and the icon if it has none.
|
|
26
32
|
*
|
|
27
33
|
* @slot - The value.
|
|
@@ -31,6 +37,8 @@ import { base, createIcon, setIconPath, type Tone } from "./styles.js";
|
|
|
31
37
|
* @csspart value - The value.
|
|
32
38
|
*
|
|
33
39
|
* @cssprop --gw-tone - The icon's color, for a tone of your own. `tone` sets it.
|
|
40
|
+
* @cssprop --gw-badge-scale - Scales the badge, around 1 for medium.
|
|
41
|
+
* @cssprop --gw-control-scale - Scales its height and icon with the theme's density. The theme sets it.
|
|
34
42
|
*/
|
|
35
43
|
export class GwBadge extends GwElement {
|
|
36
44
|
static override properties = {
|
|
@@ -43,6 +51,7 @@ export class GwBadge extends GwElement {
|
|
|
43
51
|
|
|
44
52
|
static override styles = [
|
|
45
53
|
base,
|
|
54
|
+
controlSizes("badge"),
|
|
46
55
|
css`
|
|
47
56
|
:host {
|
|
48
57
|
display: inline-flex;
|
|
@@ -50,17 +59,21 @@ export class GwBadge extends GwElement {
|
|
|
50
59
|
vertical-align: middle;
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
/* 36px tall, 12px padding, 8px between the 18px icon and
|
|
62
|
+
/* 36px tall at medium, 12px padding, 8px between the 18px icon and
|
|
63
|
+
the text */
|
|
54
64
|
.badge {
|
|
55
|
-
--gw-
|
|
65
|
+
--gw-size: var(--gw-badge-scale, 1);
|
|
66
|
+
--gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1));
|
|
67
|
+
--gw-icon-size: calc(1.125rem * var(--gw-scale));
|
|
56
68
|
|
|
57
69
|
display: inline-flex;
|
|
58
|
-
gap: var(--gw-space-2);
|
|
70
|
+
gap: calc(var(--gw-space-2) * var(--gw-size));
|
|
59
71
|
align-items: center;
|
|
60
72
|
box-sizing: border-box;
|
|
61
73
|
max-width: 100%;
|
|
62
|
-
height: 2.25rem;
|
|
63
|
-
padding: 0 var(--gw-space-4)
|
|
74
|
+
height: calc(2.25rem * var(--gw-scale));
|
|
75
|
+
padding: 0 calc(var(--gw-space-4) * var(--gw-size)) 0
|
|
76
|
+
calc(var(--gw-space-3) * var(--gw-size));
|
|
64
77
|
vertical-align: middle;
|
|
65
78
|
color: var(--gw-color-text-strong);
|
|
66
79
|
background: var(--gw-color-surface-raised);
|
|
@@ -68,14 +81,6 @@ export class GwBadge extends GwElement {
|
|
|
68
81
|
border-radius: var(--gw-radius-pill);
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
:host([size="small"]) .badge {
|
|
72
|
-
--gw-icon-size: 1rem;
|
|
73
|
-
|
|
74
|
-
gap: var(--gw-space-1);
|
|
75
|
-
height: 1.75rem;
|
|
76
|
-
padding: 0 var(--gw-space-3) 0 var(--gw-space-2);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
84
|
.icon {
|
|
80
85
|
color: var(--gw-tone, var(--gw-color-accent));
|
|
81
86
|
}
|
|
@@ -87,13 +92,19 @@ export class GwBadge extends GwElement {
|
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
.label {
|
|
90
|
-
font-size:
|
|
95
|
+
font-size: max(
|
|
96
|
+
var(--gw-font-size-2xs),
|
|
97
|
+
calc(var(--gw-font-size-2xs) * var(--gw-size))
|
|
98
|
+
);
|
|
91
99
|
line-height: 1.1;
|
|
92
100
|
color: var(--gw-color-text-secondary);
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
.content {
|
|
96
|
-
font-size:
|
|
104
|
+
font-size: max(
|
|
105
|
+
var(--gw-font-size-2xs),
|
|
106
|
+
calc(var(--gw-font-size-xs) * var(--gw-size))
|
|
107
|
+
);
|
|
97
108
|
font-weight: var(--gw-font-weight-strong);
|
|
98
109
|
line-height: 1.2;
|
|
99
110
|
white-space: nowrap;
|
|
@@ -108,7 +119,7 @@ export class GwBadge extends GwElement {
|
|
|
108
119
|
declare icon: string;
|
|
109
120
|
declare label: string;
|
|
110
121
|
declare tone: Tone;
|
|
111
|
-
declare size: "small" | "medium";
|
|
122
|
+
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
112
123
|
declare loading: boolean;
|
|
113
124
|
|
|
114
125
|
readonly #badge = h("span", { class: "badge", part: "badge" });
|
package/src/gw-button.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { GwElement, css, h, setAttribute } from "./element.js";
|
|
8
|
-
import { base, createIcon, setIconPath } from "./styles.js";
|
|
8
|
+
import { base, controlSizes, createIcon, setIconPath } from "./styles.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A native button styled through `--gw-*` variables, so it follows the
|
|
@@ -46,6 +46,7 @@ import { base, createIcon, setIconPath } from "./styles.js";
|
|
|
46
46
|
* @csspart button - The native button or link, or the pill around a slotted link.
|
|
47
47
|
*
|
|
48
48
|
* @cssprop --gw-button-scale - Scales every dimension, around 1 for medium.
|
|
49
|
+
* @cssprop --gw-control-scale - Scales its height and icon with the theme's density. The theme sets it.
|
|
49
50
|
* @cssprop --gw-button-tone - The color, set by `variant`.
|
|
50
51
|
* @cssprop --gw-button-ink - The text color, when it differs from the tone.
|
|
51
52
|
*/
|
|
@@ -71,6 +72,7 @@ export class GwButton extends GwElement {
|
|
|
71
72
|
|
|
72
73
|
static override styles = [
|
|
73
74
|
base,
|
|
75
|
+
controlSizes("button"),
|
|
74
76
|
css`
|
|
75
77
|
:host {
|
|
76
78
|
--gw-button-tone: var(--gw-color-accent);
|
|
@@ -78,25 +80,6 @@ export class GwButton extends GwElement {
|
|
|
78
80
|
display: inline-block;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
/* Everything scales from --gw-button-scale, around a 40px medium
|
|
82
|
-
button: the presets give 24, 32, 48 and 56px, and an app can set any
|
|
83
|
-
value for a size between or beyond them */
|
|
84
|
-
:host([size="xs"]) {
|
|
85
|
-
--gw-button-scale: 0.6;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
:host([size="small"]) {
|
|
89
|
-
--gw-button-scale: 0.8;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
:host([size="large"]) {
|
|
93
|
-
--gw-button-scale: 1.2;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
:host([size="xl"]) {
|
|
97
|
-
--gw-button-scale: 1.4;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
83
|
:host([variant="neutral"]) {
|
|
101
84
|
--gw-button-tone: var(--gw-color-text-secondary);
|
|
102
85
|
--gw-button-ink: var(--gw-color-text-strong);
|
|
@@ -119,18 +102,19 @@ export class GwButton extends GwElement {
|
|
|
119
102
|
}
|
|
120
103
|
|
|
121
104
|
.button {
|
|
122
|
-
--gw-
|
|
105
|
+
--gw-size: var(--gw-button-scale, 1);
|
|
106
|
+
--gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1));
|
|
123
107
|
--gw-icon-size: calc(1.125rem * var(--gw-scale));
|
|
124
108
|
|
|
125
109
|
position: relative;
|
|
126
110
|
display: inline-flex;
|
|
127
|
-
gap: calc(var(--gw-space-2) * var(--gw-
|
|
111
|
+
gap: calc(var(--gw-space-2) * var(--gw-size));
|
|
128
112
|
align-items: center;
|
|
129
113
|
justify-content: center;
|
|
130
114
|
min-height: calc(2.5rem * var(--gw-scale));
|
|
131
|
-
padding: 0 calc(var(--gw-space-4) * var(--gw-
|
|
115
|
+
padding: 0 calc(var(--gw-space-4) * var(--gw-size));
|
|
132
116
|
font: inherit;
|
|
133
|
-
font-size: calc(var(--gw-font-size-s) * var(--gw-
|
|
117
|
+
font-size: calc(var(--gw-font-size-s) * var(--gw-size));
|
|
134
118
|
font-weight: var(--gw-font-weight-strong);
|
|
135
119
|
line-height: 1;
|
|
136
120
|
color: var(--gw-button-ink, var(--gw-button-tone));
|
package/src/gw-card.ts
CHANGED
|
@@ -15,6 +15,12 @@ import { base } from "./styles.js";
|
|
|
15
15
|
* stand in for the content, and for the heading until it's known. The card
|
|
16
16
|
* glides to its new height and the content fades in once it's loaded.
|
|
17
17
|
*
|
|
18
|
+
* For a link the page's HTML has to carry, such as one with a `target` or
|
|
19
|
+
* `rel`, put an `<a>` in the `heading` slot instead of `href`. A click
|
|
20
|
+
* anywhere on the card that isn't on another control follows it, the card
|
|
21
|
+
* shows the link's hover and focus, and the link stays a real one in the
|
|
22
|
+
* page, so the rest of the card's content stays plain text.
|
|
23
|
+
*
|
|
18
24
|
* @element gw-card
|
|
19
25
|
* @summary A surface for one group of content, or a link to more.
|
|
20
26
|
*
|
|
@@ -25,6 +31,7 @@ import { base } from "./styles.js";
|
|
|
25
31
|
* @attr {number} lines - How many skeleton lines while loading. Defaults to 3.
|
|
26
32
|
*
|
|
27
33
|
* @slot - The content.
|
|
34
|
+
* @slot heading - The heading, in place of `heading`. An `<a>` here makes the whole card follow it.
|
|
28
35
|
* @slot actions - Buttons beside the heading.
|
|
29
36
|
*
|
|
30
37
|
* @csspart card - The card's surface.
|
|
@@ -71,10 +78,32 @@ export class GwCard extends GwElement {
|
|
|
71
78
|
transition: border-color var(--gw-motion-fast) var(--gw-motion-easing);
|
|
72
79
|
}
|
|
73
80
|
|
|
74
|
-
a.card:hover
|
|
81
|
+
a.card:hover,
|
|
82
|
+
.card.linked:hover {
|
|
75
83
|
border-color: var(--gw-color-border-accent);
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
/* A card following a link in its heading slot */
|
|
87
|
+
.card.linked {
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
transition: border-color var(--gw-motion-fast) var(--gw-motion-easing);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.card.focused {
|
|
93
|
+
outline: var(--gw-focus-width) solid var(--gw-color-focus);
|
|
94
|
+
outline-offset: var(--gw-focus-offset);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
::slotted([slot="heading"]) {
|
|
98
|
+
font: inherit;
|
|
99
|
+
color: inherit;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
::slotted(a[slot="heading"]) {
|
|
103
|
+
text-decoration: none;
|
|
104
|
+
outline: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
78
107
|
header {
|
|
79
108
|
display: flex;
|
|
80
109
|
gap: var(--gw-space-2);
|
|
@@ -108,10 +137,18 @@ export class GwCard extends GwElement {
|
|
|
108
137
|
|
|
109
138
|
readonly #titleSkeleton = h("gw-skeleton-text");
|
|
110
139
|
|
|
140
|
+
readonly #headingSlot = h("slot", { name: "heading" });
|
|
141
|
+
|
|
111
142
|
readonly #header = h(
|
|
112
143
|
"header",
|
|
113
144
|
{ part: "header" },
|
|
114
|
-
h(
|
|
145
|
+
h(
|
|
146
|
+
"h2",
|
|
147
|
+
{ part: "heading" },
|
|
148
|
+
this.#title,
|
|
149
|
+
this.#headingSlot,
|
|
150
|
+
this.#titleSkeleton,
|
|
151
|
+
),
|
|
115
152
|
h("slot", { name: "actions" }),
|
|
116
153
|
);
|
|
117
154
|
|
|
@@ -132,6 +169,57 @@ export class GwCard extends GwElement {
|
|
|
132
169
|
this.flush = false;
|
|
133
170
|
this.loading = false;
|
|
134
171
|
this.lines = 3;
|
|
172
|
+
|
|
173
|
+
// A click on the card, but not on another control in it, follows the
|
|
174
|
+
// link in the heading slot
|
|
175
|
+
this.addEventListener("click", (event) => {
|
|
176
|
+
const link = this.#link();
|
|
177
|
+
|
|
178
|
+
if (link === null || this.loading) return;
|
|
179
|
+
|
|
180
|
+
const path = event.composedPath();
|
|
181
|
+
const own = path.slice(0, path.indexOf(this));
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
own.some(
|
|
185
|
+
(node) =>
|
|
186
|
+
node instanceof HTMLElement &&
|
|
187
|
+
node.matches(
|
|
188
|
+
"a, button, input, select, textarea, label, [tabindex], gw-button",
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Selecting text in the card shouldn't open the link
|
|
196
|
+
if (getSelection()?.toString()) return;
|
|
197
|
+
|
|
198
|
+
link.click();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const focus = () => {
|
|
202
|
+
this.#card.classList.toggle(
|
|
203
|
+
"focused",
|
|
204
|
+
this.#link()?.matches(":focus-visible") === true,
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
this.addEventListener("focusin", focus);
|
|
209
|
+
this.addEventListener("focusout", focus);
|
|
210
|
+
|
|
211
|
+
this.#headingSlot.addEventListener("slotchange", () => {
|
|
212
|
+
this.requestUpdate("heading");
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The `<a>` in the heading slot, or holding it, when there is one. */
|
|
217
|
+
#link(): HTMLAnchorElement | null {
|
|
218
|
+
const heading = this.querySelector(":scope > [slot='heading']");
|
|
219
|
+
|
|
220
|
+
if (heading instanceof HTMLAnchorElement) return heading;
|
|
221
|
+
|
|
222
|
+
return heading?.querySelector("a") ?? null;
|
|
135
223
|
}
|
|
136
224
|
|
|
137
225
|
protected override create(root: ShadowRoot) {
|
|
@@ -163,9 +251,14 @@ export class GwCard extends GwElement {
|
|
|
163
251
|
if (this.#card instanceof HTMLAnchorElement) this.#card.href = this.href;
|
|
164
252
|
|
|
165
253
|
setAttribute(this.#card, "aria-busy", this.loading ? "true" : null);
|
|
254
|
+
|
|
255
|
+
const slotted = this.querySelector(":scope > [slot='heading']") !== null;
|
|
256
|
+
|
|
257
|
+
this.#card.classList.toggle("linked", this.#link() !== null && !this.href);
|
|
166
258
|
this.#title.textContent = this.heading;
|
|
167
|
-
this.#titleSkeleton.hidden =
|
|
168
|
-
|
|
259
|
+
this.#titleSkeleton.hidden =
|
|
260
|
+
this.heading !== "" || slotted || !this.loading;
|
|
261
|
+
this.#header.hidden = !this.heading && !slotted && !this.loading;
|
|
169
262
|
this.#skeleton.lines = this.lines;
|
|
170
263
|
this.#skeleton.hidden = !this.loading;
|
|
171
264
|
this.#body.hidden = this.loading;
|
package/src/gw-chip.ts
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
import { GwElement, css, h, setAttribute } from "./element.js";
|
|
8
8
|
import "./gw-skeleton.js";
|
|
9
9
|
import { fadeIn } from "./motion.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
base,
|
|
12
|
+
controlSizes,
|
|
13
|
+
createIcon,
|
|
14
|
+
setIconPath,
|
|
15
|
+
type Tone,
|
|
16
|
+
} from "./styles.js";
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* A short status or category, tinted by its `tone`. While `loading`, a
|
|
@@ -19,6 +25,7 @@ import { base, createIcon, setIconPath, type Tone } from "./styles.js";
|
|
|
19
25
|
* @attr {"neutral"|"accent"|"info"|"success"|"warning"|"danger"|"important"} tone - The tint. Defaults to `neutral`.
|
|
20
26
|
* @attr {string} icon - SVG path data for an icon before the text.
|
|
21
27
|
* @attr {boolean} loading - Shows a skeleton for the text.
|
|
28
|
+
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The size, around 32px tall at medium. Defaults to `medium`.
|
|
22
29
|
*
|
|
23
30
|
* @slot - The text.
|
|
24
31
|
* @slot icon - An icon before the text, in place of `icon`.
|
|
@@ -27,37 +34,47 @@ import { base, createIcon, setIconPath, type Tone } from "./styles.js";
|
|
|
27
34
|
*
|
|
28
35
|
* @cssprop --gw-tone-fill - The tint, for a tone of your own. `tone` sets it.
|
|
29
36
|
* @cssprop --gw-tone-border - The border. `tone` sets it.
|
|
37
|
+
* @cssprop --gw-chip-scale - Scales the chip, around 1 for medium.
|
|
38
|
+
* @cssprop --gw-control-scale - Scales its height and icon with the theme's density. The theme sets it.
|
|
30
39
|
*/
|
|
31
40
|
export class GwChip extends GwElement {
|
|
32
41
|
static override properties = {
|
|
33
42
|
tone: { reflect: true },
|
|
34
43
|
icon: {},
|
|
35
44
|
loading: { type: "boolean", reflect: true },
|
|
45
|
+
size: { reflect: true },
|
|
36
46
|
} as const;
|
|
37
47
|
|
|
38
48
|
static override styles = [
|
|
39
49
|
base,
|
|
50
|
+
controlSizes("chip"),
|
|
40
51
|
css`
|
|
41
52
|
:host {
|
|
42
53
|
display: inline-flex;
|
|
43
54
|
}
|
|
44
55
|
|
|
45
|
-
/* 32px, with 12px padding */
|
|
56
|
+
/* 32px at medium, with 12px padding */
|
|
46
57
|
.chip {
|
|
58
|
+
--gw-size: var(--gw-chip-scale, 1);
|
|
59
|
+
--gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1));
|
|
60
|
+
--gw-icon-size: calc(1rem * var(--gw-scale));
|
|
61
|
+
|
|
47
62
|
display: inline-flex;
|
|
48
|
-
gap: var(--gw-space-2);
|
|
63
|
+
gap: calc(var(--gw-space-2) * var(--gw-size));
|
|
49
64
|
align-items: center;
|
|
50
65
|
box-sizing: border-box;
|
|
51
|
-
height: 2rem;
|
|
52
|
-
padding: 0 var(--gw-space-3);
|
|
53
|
-
font-size:
|
|
66
|
+
height: calc(2rem * var(--gw-scale));
|
|
67
|
+
padding: 0 calc(var(--gw-space-3) * var(--gw-size));
|
|
68
|
+
font-size: max(
|
|
69
|
+
var(--gw-font-size-2xs),
|
|
70
|
+
calc(var(--gw-font-size-xs) * var(--gw-size))
|
|
71
|
+
);
|
|
54
72
|
font-weight: var(--gw-font-weight-strong);
|
|
55
73
|
color: var(--gw-color-text-strong);
|
|
56
74
|
background: var(--gw-tone-fill, var(--gw-color-fill-neutral-resting));
|
|
57
75
|
border: var(--gw-border-width) solid
|
|
58
76
|
var(--gw-tone-border, var(--gw-color-border-quiet));
|
|
59
77
|
border-radius: var(--gw-radius-chip);
|
|
60
|
-
--gw-icon-size: 1rem;
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
.content {
|
|
@@ -72,6 +89,7 @@ export class GwChip extends GwElement {
|
|
|
72
89
|
/** SVG path data for an icon before the text, instead of the `icon` slot. */
|
|
73
90
|
declare icon: string;
|
|
74
91
|
declare loading: boolean;
|
|
92
|
+
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
75
93
|
|
|
76
94
|
readonly #icon = createIcon("");
|
|
77
95
|
|
|
@@ -93,6 +111,7 @@ export class GwChip extends GwElement {
|
|
|
93
111
|
this.tone = "neutral";
|
|
94
112
|
this.icon = "";
|
|
95
113
|
this.loading = false;
|
|
114
|
+
this.size = "medium";
|
|
96
115
|
}
|
|
97
116
|
|
|
98
117
|
protected override create(root: ShadowRoot) {
|
package/src/gw-input.ts
CHANGED
|
@@ -25,6 +25,10 @@ import { LightElement, fieldId } from "./fields.js";
|
|
|
25
25
|
* @attr {string} error - An error under the field, which marks it invalid.
|
|
26
26
|
* @attr {boolean} required - Requires a value.
|
|
27
27
|
* @attr {boolean} disabled - Disables the field.
|
|
28
|
+
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The field's size, 40px tall at medium. Defaults to `medium`.
|
|
29
|
+
*
|
|
30
|
+
* @cssprop --gw-field-scale - Scales the field, around 1 for medium.
|
|
31
|
+
* @cssprop --gw-control-scale - Scales its height with the theme's density. The theme sets it.
|
|
28
32
|
*/
|
|
29
33
|
export class GwInput extends LightElement {
|
|
30
34
|
static override properties = {
|
|
@@ -39,6 +43,7 @@ export class GwInput extends LightElement {
|
|
|
39
43
|
error: {},
|
|
40
44
|
required: { type: "boolean" },
|
|
41
45
|
disabled: { type: "boolean" },
|
|
46
|
+
size: { reflect: true },
|
|
42
47
|
} as const;
|
|
43
48
|
|
|
44
49
|
declare label: string;
|
|
@@ -53,6 +58,7 @@ export class GwInput extends LightElement {
|
|
|
53
58
|
declare error: string;
|
|
54
59
|
declare required: boolean;
|
|
55
60
|
declare disabled: boolean;
|
|
61
|
+
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
56
62
|
|
|
57
63
|
readonly #id = fieldId();
|
|
58
64
|
|
|
@@ -79,6 +85,7 @@ export class GwInput extends LightElement {
|
|
|
79
85
|
this.error = "";
|
|
80
86
|
this.required = false;
|
|
81
87
|
this.disabled = false;
|
|
88
|
+
this.size = "medium";
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
protected override create(root: HTMLElement) {
|
package/src/gw-menu-button.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { GwElement, css, h } from "./element.js";
|
|
8
|
-
import { base, createIcon, setIconPath } from "./styles.js";
|
|
8
|
+
import { base, controlSizes, createIcon, setIconPath } from "./styles.js";
|
|
9
9
|
|
|
10
10
|
export interface MenuItem {
|
|
11
11
|
readonly value: string;
|
|
@@ -38,9 +38,13 @@ export interface MenuItem {
|
|
|
38
38
|
* @attr {string} empty - What the menu says with no items. Defaults to `Nothing here yet`.
|
|
39
39
|
* @attr {MenuItem[]} items - The items, as JSON.
|
|
40
40
|
* @attr {MenuItem[]} actions - Actions pinned under the items, as JSON.
|
|
41
|
+
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The trigger's size, 24 to 56px tall. Defaults to `medium`.
|
|
41
42
|
*
|
|
42
43
|
* @csspart trigger - The button that opens the menu.
|
|
43
44
|
*
|
|
45
|
+
* @cssprop --gw-menu-button-scale - Scales the trigger, around 1 for medium.
|
|
46
|
+
* @cssprop --gw-control-scale - Scales its height and icon with the theme's density. The theme sets it.
|
|
47
|
+
*
|
|
44
48
|
* @fires {CustomEvent<{ value: string }>} select - When an item or action is chosen, with its `value` in `detail`.
|
|
45
49
|
*/
|
|
46
50
|
export class GwMenuButton extends GwElement {
|
|
@@ -50,10 +54,12 @@ export class GwMenuButton extends GwElement {
|
|
|
50
54
|
empty: {},
|
|
51
55
|
items: { type: "json" },
|
|
52
56
|
actions: { type: "json" },
|
|
57
|
+
size: { reflect: true },
|
|
53
58
|
} as const;
|
|
54
59
|
|
|
55
60
|
static override styles = [
|
|
56
61
|
base,
|
|
62
|
+
controlSizes("menu-button"),
|
|
57
63
|
css`
|
|
58
64
|
:host {
|
|
59
65
|
position: relative;
|
|
@@ -62,14 +68,18 @@ export class GwMenuButton extends GwElement {
|
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
.trigger {
|
|
71
|
+
--gw-size: var(--gw-menu-button-scale, 1);
|
|
72
|
+
--gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1));
|
|
73
|
+
--gw-icon-size: calc(1.25rem * var(--gw-scale));
|
|
74
|
+
|
|
65
75
|
display: inline-flex;
|
|
66
|
-
gap: var(--gw-space-2);
|
|
76
|
+
gap: calc(var(--gw-space-2) * var(--gw-size));
|
|
67
77
|
align-items: center;
|
|
68
78
|
box-sizing: border-box;
|
|
69
|
-
height: 2.5rem;
|
|
70
|
-
padding: 0 var(--gw-space-4);
|
|
79
|
+
height: calc(2.5rem * var(--gw-scale));
|
|
80
|
+
padding: 0 calc(var(--gw-space-4) * var(--gw-size));
|
|
71
81
|
font: inherit;
|
|
72
|
-
font-size: var(--gw-font-size-s);
|
|
82
|
+
font-size: calc(var(--gw-font-size-s) * var(--gw-size));
|
|
73
83
|
font-weight: var(--gw-font-weight-strong);
|
|
74
84
|
color: var(--gw-color-text-strong);
|
|
75
85
|
cursor: pointer;
|
|
@@ -233,6 +243,7 @@ export class GwMenuButton extends GwElement {
|
|
|
233
243
|
declare empty: string;
|
|
234
244
|
declare items: readonly MenuItem[];
|
|
235
245
|
declare actions: readonly MenuItem[];
|
|
246
|
+
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
236
247
|
|
|
237
248
|
readonly #trigger = h("button", {
|
|
238
249
|
class: "trigger",
|
|
@@ -264,6 +275,7 @@ export class GwMenuButton extends GwElement {
|
|
|
264
275
|
this.empty = "Nothing here yet";
|
|
265
276
|
this.items = [];
|
|
266
277
|
this.actions = [];
|
|
278
|
+
this.size = "medium";
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
/** The enabled items then actions, in the order arrow keys move through them. */
|
package/src/gw-segmented.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { GwElement, css, h, setAttribute } from "./element.js";
|
|
8
|
-
import { base, createIcon } from "./styles.js";
|
|
8
|
+
import { base, controlSizes, createIcon } from "./styles.js";
|
|
9
9
|
|
|
10
10
|
export interface Segment {
|
|
11
11
|
readonly value: string;
|
|
@@ -31,10 +31,14 @@ export interface Segment {
|
|
|
31
31
|
* @attr {string} value - The chosen option's value.
|
|
32
32
|
* @attr {"show"|"hide"|"auto"} labels - Whether option text shows beside icons. Defaults to `show`.
|
|
33
33
|
* @attr {Segment[]} options - The options, as JSON.
|
|
34
|
+
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The size, 40px tall at medium. Defaults to `medium`.
|
|
34
35
|
*
|
|
35
36
|
* @csspart option - Each option.
|
|
36
37
|
* @csspart indicator - The pill that slides to the chosen option.
|
|
37
38
|
*
|
|
39
|
+
* @cssprop --gw-segmented-scale - Scales the control, around 1 for medium.
|
|
40
|
+
* @cssprop --gw-control-scale - Scales its height and icons with the theme's density. The theme sets it.
|
|
41
|
+
*
|
|
38
42
|
* @fires {Event} change - When the chosen option changes.
|
|
39
43
|
*/
|
|
40
44
|
export class GwSegmented extends GwElement {
|
|
@@ -44,12 +48,17 @@ export class GwSegmented extends GwElement {
|
|
|
44
48
|
value: { reflect: true },
|
|
45
49
|
labels: { reflect: true },
|
|
46
50
|
options: { type: "json" },
|
|
51
|
+
size: { reflect: true },
|
|
47
52
|
} as const;
|
|
48
53
|
|
|
49
54
|
static override styles = [
|
|
50
55
|
base,
|
|
56
|
+
controlSizes("segmented"),
|
|
51
57
|
css`
|
|
52
58
|
:host {
|
|
59
|
+
--gw-size: var(--gw-segmented-scale, 1);
|
|
60
|
+
--gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1));
|
|
61
|
+
|
|
53
62
|
display: inline-block;
|
|
54
63
|
}
|
|
55
64
|
|
|
@@ -78,20 +87,21 @@ export class GwSegmented extends GwElement {
|
|
|
78
87
|
label {
|
|
79
88
|
position: relative;
|
|
80
89
|
display: inline-flex;
|
|
81
|
-
gap: var(--gw-space-2);
|
|
90
|
+
gap: calc(var(--gw-space-2) * var(--gw-size));
|
|
82
91
|
align-items: center;
|
|
83
92
|
box-sizing: border-box;
|
|
84
|
-
/* 32px options in a 40px track, matching fields and
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
/* 32px options in a 40px track at medium, matching fields and
|
|
94
|
+
buttons */
|
|
95
|
+
height: calc(2rem * var(--gw-scale));
|
|
96
|
+
padding: 0 calc(var(--gw-space-3) * var(--gw-size));
|
|
97
|
+
font-size: calc(var(--gw-font-size-s) * var(--gw-size));
|
|
88
98
|
color: var(--gw-color-text-primary);
|
|
89
99
|
cursor: pointer;
|
|
90
100
|
background: transparent;
|
|
91
101
|
border-radius: var(--gw-radius-pill);
|
|
92
102
|
transition: background-color var(--gw-motion-fast)
|
|
93
103
|
var(--gw-motion-easing);
|
|
94
|
-
--gw-icon-size: 1.125rem;
|
|
104
|
+
--gw-icon-size: calc(1.125rem * var(--gw-scale));
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
:host([labels="hide"]) label.has-icon {
|
|
@@ -188,6 +198,7 @@ export class GwSegmented extends GwElement {
|
|
|
188
198
|
declare value: string;
|
|
189
199
|
declare labels: "show" | "hide" | "auto";
|
|
190
200
|
declare options: readonly Segment[];
|
|
201
|
+
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
191
202
|
|
|
192
203
|
constructor() {
|
|
193
204
|
super();
|
|
@@ -196,6 +207,7 @@ export class GwSegmented extends GwElement {
|
|
|
196
207
|
this.value = "";
|
|
197
208
|
this.labels = "show";
|
|
198
209
|
this.options = [];
|
|
210
|
+
this.size = "medium";
|
|
199
211
|
}
|
|
200
212
|
|
|
201
213
|
readonly #legend = h("legend");
|