@timmo001/groundwork-ui 0.7.0 → 0.8.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.
@@ -3103,7 +3103,7 @@
3103
3103
  "text": "Properties"
3104
3104
  },
3105
3105
  "static": true,
3106
- "default": "{ variant: { reflect: true }, appearance: { reflect: true }, size: { reflect: true }, type: {}, href: {}, label: {}, disabled: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, icon: {}, labels: { reflect: true }, }",
3106
+ "default": "{ variant: { reflect: true }, appearance: { reflect: true }, size: { reflect: true }, type: {}, href: {}, label: {}, disabled: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, icon: {}, swatch: {}, labels: { reflect: true }, }",
3107
3107
  "inheritedFrom": {
3108
3108
  "name": "GwElement",
3109
3109
  "module": "src/element.ts"
@@ -3130,7 +3130,7 @@
3130
3130
  "text": "readonly CSSStyleSheet[]"
3131
3131
  },
3132
3132
  "static": true,
3133
- "default": "[ base, controlSizes(\"button\"), css` :host { --gw-button-tone: var(--gw-color-accent); display: inline-block; } :host([variant=\"neutral\"]) { --gw-button-tone: var(--gw-color-text-secondary); --gw-button-ink: var(--gw-color-text-strong); } :host([variant=\"info\"]) { --gw-button-tone: var(--gw-color-info); } :host([variant=\"success\"]) { --gw-button-tone: var(--gw-color-success); } :host([variant=\"warning\"]) { --gw-button-tone: var(--gw-color-warning); } :host([variant=\"danger\"]) { --gw-button-tone: var(--gw-color-danger); } .button { --gw-size: var(--gw-button-scale, 1); --gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1)); --gw-icon-size: calc(1.125rem * var(--gw-scale)); position: relative; display: inline-flex; gap: calc(var(--gw-space-2) * var(--gw-size)); align-items: center; justify-content: center; min-height: calc(2.5rem * var(--gw-scale)); padding: 0 calc(var(--gw-space-4) * var(--gw-size)); font: inherit; font-size: calc(var(--gw-font-size-s) * var(--gw-size)); font-weight: var(--gw-font-weight-strong); line-height: 1; color: var(--gw-button-ink, var(--gw-button-tone)); text-decoration: none; text-transform: var(--gw-label-transform); letter-spacing: var(--gw-label-tracking); white-space: nowrap; cursor: pointer; background: transparent; border: max(var(--gw-border-width), 1px) solid transparent; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing), border-color var(--gw-motion-fast) var(--gw-motion-easing), color var(--gw-motion-fast) var(--gw-motion-easing), scale var(--gw-motion-fast) var(--gw-motion-easing); } /* A slight press, so a click feels physical */ .button:active { scale: 0.97; } .button:hover { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } .button:active { background: color-mix(in oklch, var(--gw-button-tone) 24%, transparent); } /* A solid fill, with text that flips between black and white by the fill's lightness so it reads on every theme's colors */ :host([appearance=\"accent\"]) .button { color: oklch( from var(--gw-button-tone) clamp(0, (0.66 - l) * 1000, 1) 0 0 ); background: var(--gw-button-tone); } :host([appearance=\"accent\"]) .button:hover { background: oklch( from var(--gw-button-tone) calc(l + (0.6 - l) * 0.2) c h ); } :host([appearance=\"accent\"]) .button:active { background: oklch( from var(--gw-button-tone) calc(l + (0.6 - l) * 0.35) c h ); } /* The tint lowers the tone's contrast, so the text leans toward the strongest text color to stay readable on it */ :host([appearance=\"filled\"]) .button { color: var( --gw-button-ink, color-mix( in oklch, var(--gw-button-tone) 70%, var(--gw-color-text-strong) ) ); background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([appearance=\"filled\"]) .button:hover { background: color-mix(in oklch, var(--gw-button-tone) 24%, transparent); } :host([appearance=\"filled\"]) .button:active { background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent); } :host([appearance=\"outlined\"]) .button { border-color: var(--gw-button-tone); } /* A slotted link carries the text; the pill draws its focus ring */ ::slotted(a) { color: inherit; text-decoration: none; outline: none; } .button.focused { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :host([appearance=\"plain\"]) .button.current, :host([appearance=\"outlined\"]) .button.current { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([appearance=\"filled\"]) .button.current { background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent); } .button:disabled, .button[aria-disabled=\"true\"] { color: var(--gw-color-text-disabled); pointer-events: none; cursor: not-allowed; background: var(--gw-color-fill-neutral-disabled); border-color: transparent; } :host([appearance=\"plain\"]) .button:disabled, :host([appearance=\"plain\"]) .button[aria-disabled=\"true\"] { background: transparent; } ::slotted([slot=\"icon\"]), .icon { flex: none; width: var(--gw-icon-size); height: var(--gw-icon-size); fill: currentColor; } /* The icon and the ring share one cell and crossfade */ .lead { display: grid; place-items: center; } .lead > * { grid-area: 1 / 1; } .lead > *, .text, .ring { transition: opacity var(--gw-motion-normal) cubic-bezier(0.4, 0, 0.2, 1); } /* Sized to the icon's drawn area, which is inset in its 24px box */ .ring { width: calc(var(--gw-icon-size) * 0.8); height: calc(var(--gw-icon-size) * 0.8); border: calc(2px * var(--gw-scale)) solid color-mix(in oklch, currentColor 25%, transparent); border-top-color: currentColor; border-radius: 50%; opacity: 0; animation: spin 0.9s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Without an icon, the ring sits over the hidden text */ .ring.over { position: absolute; inset: 0; margin: auto; } :host([loading]) .button { cursor: progress; } :host([loading]) .lead > .ring, :host([loading]) .button:not(.has-icon) > .ring.over { opacity: 1; } :host([loading]) .lead > :not(.ring), :host([loading]) .button:not(.has-icon) > .text { opacity: 0; } .button:not(.has-icon) > .lead { display: none; } /* Icon only, to save space: square, with the text kept for assistive technology. auto does this on narrow screens */ :host([labels=\"hide\"]) .button.has-icon { min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"hide\"]) .has-icon .text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } @media (width < 40rem) { :host([labels=\"auto\"]) .button.has-icon { min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"auto\"]) .has-icon .text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } } `, ]",
3133
+ "default": "[ base, controlSizes(\"button\"), css` :host { --gw-button-tone: var(--gw-color-accent); display: inline-block; } :host([variant=\"neutral\"]) { --gw-button-tone: var(--gw-color-text-secondary); --gw-button-ink: var(--gw-color-text-strong); } :host([variant=\"info\"]) { --gw-button-tone: var(--gw-color-info); } :host([variant=\"success\"]) { --gw-button-tone: var(--gw-color-success); } :host([variant=\"warning\"]) { --gw-button-tone: var(--gw-color-warning); } :host([variant=\"danger\"]) { --gw-button-tone: var(--gw-color-danger); } .button { --gw-size: var(--gw-button-scale, 1); --gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1)); --gw-icon-size: calc(1.125rem * var(--gw-scale)); position: relative; display: inline-flex; gap: calc(var(--gw-space-2) * var(--gw-size)); align-items: center; justify-content: center; min-height: calc(2.5rem * var(--gw-scale)); padding: 0 calc(var(--gw-space-4) * var(--gw-size)); font: inherit; font-size: calc(var(--gw-font-size-s) * var(--gw-size)); font-weight: var(--gw-font-weight-strong); line-height: 1; color: var(--gw-button-ink, var(--gw-button-tone)); text-decoration: none; text-transform: var(--gw-label-transform); letter-spacing: var(--gw-label-tracking); white-space: nowrap; cursor: pointer; background: transparent; border: max(var(--gw-border-width), 1px) solid transparent; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing), border-color var(--gw-motion-fast) var(--gw-motion-easing), color var(--gw-motion-fast) var(--gw-motion-easing), scale var(--gw-motion-fast) var(--gw-motion-easing); } /* A slight press, so a click feels physical */ .button:active { scale: 0.97; } .button:hover { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } .button:active { background: color-mix(in oklch, var(--gw-button-tone) 24%, transparent); } /* A solid fill, with text that flips between black and white by the fill's lightness so it reads on every theme's colors */ :host([appearance=\"accent\"]) .button { color: oklch( from var(--gw-button-tone) clamp(0, (0.66 - l) * 1000, 1) 0 0 ); background: var(--gw-button-tone); } :host([appearance=\"accent\"]) .button:hover { background: oklch( from var(--gw-button-tone) calc(l + (0.6 - l) * 0.2) c h ); } :host([appearance=\"accent\"]) .button:active { background: oklch( from var(--gw-button-tone) calc(l + (0.6 - l) * 0.35) c h ); } /* The tint lowers the tone's contrast, so the text leans toward the strongest text color to stay readable on it */ :host([appearance=\"filled\"]) .button { color: var( --gw-button-ink, color-mix( in oklch, var(--gw-button-tone) 70%, var(--gw-color-text-strong) ) ); background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([appearance=\"filled\"]) .button:hover { background: color-mix(in oklch, var(--gw-button-tone) 24%, transparent); } :host([appearance=\"filled\"]) .button:active { background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent); } :host([appearance=\"outlined\"]) .button { border-color: var(--gw-button-tone); } /* A slotted link carries the text; the pill draws its focus ring */ ::slotted(a) { color: inherit; text-decoration: none; outline: none; } .button.focused { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :host([appearance=\"plain\"]) .button.current, :host([appearance=\"outlined\"]) .button.current { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([appearance=\"filled\"]) .button.current { background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent); } .button:disabled, .button[aria-disabled=\"true\"] { color: var(--gw-color-text-disabled); pointer-events: none; cursor: not-allowed; background: var(--gw-color-fill-neutral-disabled); border-color: transparent; } :host([appearance=\"plain\"]) .button:disabled, :host([appearance=\"plain\"]) .button[aria-disabled=\"true\"] { background: transparent; } ::slotted([slot=\"icon\"]), .icon { flex: none; width: var(--gw-icon-size); height: var(--gw-icon-size); fill: currentColor; } /* The icon and the ring share one cell and crossfade */ .lead { display: grid; place-items: center; } /* The icon slot is display: contents, so its slotted icon is a grid item of its own and needs the shared cell too */ .lead > *, ::slotted([slot=\"icon\"]) { grid-area: 1 / 1; } /* A color dot in the icon's place */ .swatch { width: calc(var(--gw-icon-size) * 0.6); height: calc(var(--gw-icon-size) * 0.6); border: 1px solid color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent); border-radius: 50%; } .lead > *, .text, .ring { transition: opacity var(--gw-motion-normal) cubic-bezier(0.4, 0, 0.2, 1); } /* Sized to the icon's drawn area, which is inset in its 24px box */ .ring { width: calc(var(--gw-icon-size) * 0.8); height: calc(var(--gw-icon-size) * 0.8); border: calc(2px * var(--gw-scale)) solid color-mix(in oklch, currentColor 25%, transparent); border-top-color: currentColor; border-radius: 50%; opacity: 0; animation: spin 0.9s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Without an icon, the ring sits over the hidden text */ .ring.over { position: absolute; inset: 0; margin: auto; } :host([loading]) .button { cursor: progress; } :host([loading]) .lead > .ring, :host([loading]) .button:not(.has-icon) > .ring.over { opacity: 1; } :host([loading]) .lead > :not(.ring), :host([loading]) .button:not(.has-icon) > .text { opacity: 0; } .button:not(.has-icon) > .lead { display: none; } /* Icon only, to save space: square, with the text kept for assistive technology. auto does this on narrow screens */ :host([labels=\"hide\"]) .button.has-icon { min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"hide\"]) .has-icon .text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } @media (width < 40rem) { :host([labels=\"auto\"]) .button.has-icon { min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"auto\"]) .has-icon .text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } } `, ]",
3134
3134
  "inheritedFrom": {
3135
3135
  "name": "GwElement",
3136
3136
  "module": "src/element.ts"
@@ -3210,6 +3210,15 @@
3210
3210
  "description": "SVG path data for an icon before the text.",
3211
3211
  "default": "\"\""
3212
3212
  },
3213
+ {
3214
+ "kind": "field",
3215
+ "name": "swatch",
3216
+ "type": {
3217
+ "text": "string"
3218
+ },
3219
+ "description": "A CSS color, drawn as a small round dot in the icon's place.",
3220
+ "default": "\"\""
3221
+ },
3213
3222
  {
3214
3223
  "kind": "field",
3215
3224
  "name": "labels",
@@ -3231,6 +3240,12 @@
3231
3240
  "privacy": "private",
3232
3241
  "readonly": true
3233
3242
  },
3243
+ {
3244
+ "kind": "field",
3245
+ "name": "#swatch",
3246
+ "privacy": "private",
3247
+ "readonly": true
3248
+ },
3234
3249
  {
3235
3250
  "kind": "field",
3236
3251
  "name": "#lead",
@@ -3630,6 +3645,13 @@
3630
3645
  "description": "SVG path data for an icon before the text.",
3631
3646
  "name": "icon"
3632
3647
  },
3648
+ {
3649
+ "type": {
3650
+ "text": "string"
3651
+ },
3652
+ "description": "A CSS color, drawn as a small round dot in the icon's place, such as a tag's own color.",
3653
+ "name": "swatch"
3654
+ },
3633
3655
  {
3634
3656
  "type": {
3635
3657
  "text": "\"show\"|\"hide\"|\"auto\""
@@ -5344,7 +5366,7 @@
5344
5366
  "text": "Properties"
5345
5367
  },
5346
5368
  "static": true,
5347
- "default": "{ tone: { reflect: true }, icon: {}, loading: { type: \"boolean\", reflect: true }, size: { reflect: true }, }",
5369
+ "default": "{ tone: { reflect: true }, icon: {}, swatch: {}, loading: { type: \"boolean\", reflect: true }, size: { reflect: true }, }",
5348
5370
  "inheritedFrom": {
5349
5371
  "name": "GwElement",
5350
5372
  "module": "src/element.ts"
@@ -5357,7 +5379,7 @@
5357
5379
  "text": "readonly CSSStyleSheet[]"
5358
5380
  },
5359
5381
  "static": true,
5360
- "default": "[ base, controlSizes(\"chip\"), css` :host { display: inline-flex; } /* 32px at medium, with 12px padding */ .chip { --gw-size: var(--gw-chip-scale, 1); --gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1)); --gw-icon-size: calc(1rem * var(--gw-scale)); display: inline-flex; gap: calc(var(--gw-space-2) * var(--gw-size)); align-items: center; box-sizing: border-box; height: calc(2rem * var(--gw-scale)); padding: 0 calc(var(--gw-space-3) * var(--gw-size)); font-size: max( var(--gw-font-size-2xs), calc(var(--gw-font-size-xs) * var(--gw-size)) ); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); background: var(--gw-tone-fill, var(--gw-color-fill-neutral-resting)); border: var(--gw-border-width) solid var(--gw-tone-border, var(--gw-color-border-quiet)); border-radius: var(--gw-radius-chip); } .content { display: inline-flex; gap: inherit; align-items: center; } `, ]",
5382
+ "default": "[ base, controlSizes(\"chip\"), css` :host { display: inline-flex; } /* 32px at medium, with 12px padding */ .chip { --gw-size: var(--gw-chip-scale, 1); --gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1)); --gw-icon-size: calc(1rem * var(--gw-scale)); display: inline-flex; gap: calc(var(--gw-space-2) * var(--gw-size)); align-items: center; box-sizing: border-box; height: calc(2rem * var(--gw-scale)); padding: 0 calc(var(--gw-space-3) * var(--gw-size)); font-size: max( var(--gw-font-size-2xs), calc(var(--gw-font-size-xs) * var(--gw-size)) ); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); background: var(--gw-tone-fill, var(--gw-color-fill-neutral-resting)); border: var(--gw-border-width) solid var(--gw-tone-border, var(--gw-color-border-quiet)); border-radius: var(--gw-radius-chip); } .content { display: inline-flex; gap: inherit; align-items: center; } /* A color dot in the icon's place */ .swatch { flex: none; width: calc(var(--gw-icon-size) * 0.6); height: calc(var(--gw-icon-size) * 0.6); border: 1px solid color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent); border-radius: 50%; } `, ]",
5361
5383
  "inheritedFrom": {
5362
5384
  "name": "GwElement",
5363
5385
  "module": "src/element.ts"
@@ -5380,6 +5402,15 @@
5380
5402
  "description": "SVG path data for an icon before the text, instead of the `icon` slot.",
5381
5403
  "default": "\"\""
5382
5404
  },
5405
+ {
5406
+ "kind": "field",
5407
+ "name": "swatch",
5408
+ "type": {
5409
+ "text": "string"
5410
+ },
5411
+ "description": "A CSS color, drawn as a small round dot in the icon's place.",
5412
+ "default": "\"\""
5413
+ },
5383
5414
  {
5384
5415
  "kind": "field",
5385
5416
  "name": "loading",
@@ -5402,6 +5433,12 @@
5402
5433
  "privacy": "private",
5403
5434
  "readonly": true
5404
5435
  },
5436
+ {
5437
+ "kind": "field",
5438
+ "name": "#swatch",
5439
+ "privacy": "private",
5440
+ "readonly": true
5441
+ },
5405
5442
  {
5406
5443
  "kind": "field",
5407
5444
  "name": "#chip",
@@ -5747,6 +5784,13 @@
5747
5784
  "description": "SVG path data for an icon before the text.",
5748
5785
  "name": "icon"
5749
5786
  },
5787
+ {
5788
+ "type": {
5789
+ "text": "string"
5790
+ },
5791
+ "description": "A CSS color, drawn as a small round dot in the icon's place, such as a tag's own color.",
5792
+ "name": "swatch"
5793
+ },
5750
5794
  {
5751
5795
  "type": {
5752
5796
  "text": "boolean"
@@ -20249,7 +20293,7 @@
20249
20293
  "text": "readonly CSSStyleSheet[]"
20250
20294
  },
20251
20295
  "static": true,
20252
- "default": "[ base, css` :host { display: block; } .page { display: flex; flex-direction: column; gap: var(--gw-space-6); width: 100%; max-width: var(--gw-content-width); margin-inline: auto; } :host([narrow]) .page { max-width: var(--gw-content-width-narrow); } `, ]",
20296
+ "default": "[ base, css` :host { display: block; } .page { display: flex; flex-direction: column; gap: var(--gw-space-6); /* The same box as gw-app-bar's content, so the two line up */ width: min(100% - 2 * var(--gw-space-3), var(--gw-content-width)); margin-inline: auto; } :host([narrow]) .page { width: min( 100% - 2 * var(--gw-space-3), var(--gw-content-width-narrow) ); } `, ]",
20253
20297
  "inheritedFrom": {
20254
20298
  "name": "GwElement",
20255
20299
  "module": "src/element.ts"
@@ -21988,7 +22032,7 @@
21988
22032
  {
21989
22033
  "kind": "variable",
21990
22034
  "name": "base",
21991
- "default": "css` :host { font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } *, *::before, *::after { box-sizing: border-box; } [hidden] { display: none !important; } :focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } :host([tone=\"accent\"]) { --gw-tone: var(--gw-color-accent); --gw-tone-fill: var(--gw-color-fill-accent-quiet); --gw-tone-border: var(--gw-color-border-accent); } :host([tone=\"info\"]) { --gw-tone: var(--gw-color-info); --gw-tone-fill: var(--gw-color-fill-info-quiet); --gw-tone-border: var(--gw-color-border-info); } :host([tone=\"success\"]) { --gw-tone: var(--gw-color-success); --gw-tone-fill: var(--gw-color-fill-success-quiet); --gw-tone-border: var(--gw-color-border-success); } :host([tone=\"warning\"]) { --gw-tone: var(--gw-color-warning); --gw-tone-fill: var(--gw-color-fill-warning-quiet); --gw-tone-border: var(--gw-color-border-warning); } :host([tone=\"danger\"]) { --gw-tone: var(--gw-color-danger); --gw-tone-fill: var(--gw-color-fill-danger-quiet); --gw-tone-border: var(--gw-color-border-danger); } :host([tone=\"important\"]) { --gw-tone: var(--gw-color-important); --gw-tone-fill: var(--gw-color-fill-important-quiet); --gw-tone-border: var(--gw-color-border-important); } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; } } `",
22035
+ "default": "css` :host { font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } *, *::before, *::after { box-sizing: border-box; } :host([hidden]), [hidden] { display: none !important; } :focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } :host([tone=\"accent\"]) { --gw-tone: var(--gw-color-accent); --gw-tone-fill: var(--gw-color-fill-accent-quiet); --gw-tone-border: var(--gw-color-border-accent); } :host([tone=\"info\"]) { --gw-tone: var(--gw-color-info); --gw-tone-fill: var(--gw-color-fill-info-quiet); --gw-tone-border: var(--gw-color-border-info); } :host([tone=\"success\"]) { --gw-tone: var(--gw-color-success); --gw-tone-fill: var(--gw-color-fill-success-quiet); --gw-tone-border: var(--gw-color-border-success); } :host([tone=\"warning\"]) { --gw-tone: var(--gw-color-warning); --gw-tone-fill: var(--gw-color-fill-warning-quiet); --gw-tone-border: var(--gw-color-border-warning); } :host([tone=\"danger\"]) { --gw-tone: var(--gw-color-danger); --gw-tone-fill: var(--gw-color-fill-danger-quiet); --gw-tone-border: var(--gw-color-border-danger); } :host([tone=\"important\"]) { --gw-tone: var(--gw-color-important); --gw-tone-fill: var(--gw-color-fill-important-quiet); --gw-tone-border: var(--gw-color-border-important); } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; } } `",
21992
22036
  "description": "What every component shares: box sizing, the focus ring, icons and the\ntones that set `--gw-tone` from a `tone` attribute."
21993
22037
  },
21994
22038
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-ui",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Groundwork's framework-free web components, styled by the --gw-* tokens",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/gw-button.ts CHANGED
@@ -38,6 +38,7 @@ import { base, controlSizes, createIcon, setIconPath } from "./styles.js";
38
38
  * @attr {boolean} disabled - Disables the button.
39
39
  * @attr {boolean} loading - Shows a spinning ring in the icon's place and ignores clicks.
40
40
  * @attr {string} icon - SVG path data for an icon before the text.
41
+ * @attr {string} swatch - A CSS color, drawn as a small round dot in the icon's place, such as a tag's own color.
41
42
  * @attr {"show"|"hide"|"auto"} labels - Whether the text shows beside an icon: always, never, or not on narrow screens. Defaults to `show`.
42
43
  *
43
44
  * @slot - The text, or an `<a>` holding it, in place of `href`.
@@ -61,6 +62,7 @@ export class GwButton extends GwElement {
61
62
  disabled: { type: "boolean", reflect: true },
62
63
  loading: { type: "boolean", reflect: true },
63
64
  icon: {},
65
+ swatch: {},
64
66
  labels: { reflect: true },
65
67
  } as const;
66
68
 
@@ -242,10 +244,22 @@ export class GwButton extends GwElement {
242
244
  place-items: center;
243
245
  }
244
246
 
245
- .lead > * {
247
+ /* The icon slot is display: contents, so its slotted icon is a grid
248
+ item of its own and needs the shared cell too */
249
+ .lead > *,
250
+ ::slotted([slot="icon"]) {
246
251
  grid-area: 1 / 1;
247
252
  }
248
253
 
254
+ /* A color dot in the icon's place */
255
+ .swatch {
256
+ width: calc(var(--gw-icon-size) * 0.6);
257
+ height: calc(var(--gw-icon-size) * 0.6);
258
+ border: 1px solid
259
+ color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent);
260
+ border-radius: 50%;
261
+ }
262
+
249
263
  .lead > *,
250
264
  .text,
251
265
  .ring {
@@ -341,6 +355,8 @@ export class GwButton extends GwElement {
341
355
  declare loading: boolean;
342
356
  /** SVG path data for an icon before the text. */
343
357
  declare icon: string;
358
+ /** A CSS color, drawn as a small round dot in the icon's place. */
359
+ declare swatch: string;
344
360
  /** Whether the text shows beside an icon: always, never, or not on narrow screens. */
345
361
  declare labels: "show" | "hide" | "auto";
346
362
 
@@ -348,10 +364,13 @@ export class GwButton extends GwElement {
348
364
 
349
365
  readonly #iconSlot = h("slot", { name: "icon" });
350
366
 
367
+ readonly #swatch = h("span", { class: "swatch", "aria-hidden": "true" });
368
+
351
369
  readonly #lead = h(
352
370
  "span",
353
371
  { class: "lead" },
354
372
  this.#icon,
373
+ this.#swatch,
355
374
  this.#iconSlot,
356
375
  h("span", { class: "ring" }),
357
376
  );
@@ -376,6 +395,7 @@ export class GwButton extends GwElement {
376
395
  constructor() {
377
396
  super();
378
397
  this.icon = "";
398
+ this.swatch = "";
379
399
  this.labels = "show";
380
400
  this.variant = "brand";
381
401
  this.appearance = "accent";
@@ -456,7 +476,9 @@ export class GwButton extends GwElement {
456
476
  const control = this.#control;
457
477
 
458
478
  const hasIcon =
459
- this.icon !== "" || this.querySelector("[slot='icon']") !== null;
479
+ this.icon !== "" ||
480
+ this.swatch !== "" ||
481
+ this.querySelector("[slot='icon']") !== null;
460
482
 
461
483
  control.classList.toggle("has-icon", hasIcon);
462
484
  control.classList.toggle(
@@ -466,6 +488,8 @@ export class GwButton extends GwElement {
466
488
  );
467
489
  setIconPath(this.#icon, this.icon);
468
490
  this.#icon.style.display = this.icon ? "" : "none";
491
+ this.#swatch.style.background = this.swatch;
492
+ this.#swatch.hidden = this.swatch === "" || this.icon !== "";
469
493
  setAttribute(
470
494
  control,
471
495
  "title",
package/src/gw-chip.ts CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  *
25
25
  * @attr {"neutral"|"accent"|"info"|"success"|"warning"|"danger"|"important"} tone - The tint. Defaults to `neutral`.
26
26
  * @attr {string} icon - SVG path data for an icon before the text.
27
+ * @attr {string} swatch - A CSS color, drawn as a small round dot in the icon's place, such as a tag's own color.
27
28
  * @attr {boolean} loading - Shows a skeleton for the text.
28
29
  * @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The size, around 32px tall at medium. Defaults to `medium`.
29
30
  *
@@ -41,6 +42,7 @@ export class GwChip extends GwElement {
41
42
  static override properties = {
42
43
  tone: { reflect: true },
43
44
  icon: {},
45
+ swatch: {},
44
46
  loading: { type: "boolean", reflect: true },
45
47
  size: { reflect: true },
46
48
  } as const;
@@ -82,17 +84,31 @@ export class GwChip extends GwElement {
82
84
  gap: inherit;
83
85
  align-items: center;
84
86
  }
87
+
88
+ /* A color dot in the icon's place */
89
+ .swatch {
90
+ flex: none;
91
+ width: calc(var(--gw-icon-size) * 0.6);
92
+ height: calc(var(--gw-icon-size) * 0.6);
93
+ border: 1px solid
94
+ color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent);
95
+ border-radius: 50%;
96
+ }
85
97
  `,
86
98
  ];
87
99
 
88
100
  declare tone: Tone;
89
101
  /** SVG path data for an icon before the text, instead of the `icon` slot. */
90
102
  declare icon: string;
103
+ /** A CSS color, drawn as a small round dot in the icon's place. */
104
+ declare swatch: string;
91
105
  declare loading: boolean;
92
106
  declare size: "xs" | "small" | "medium" | "large" | "xl";
93
107
 
94
108
  readonly #icon = createIcon("");
95
109
 
110
+ readonly #swatch = h("span", { class: "swatch", "aria-hidden": "true" });
111
+
96
112
  readonly #chip = h("span", { class: "chip", part: "chip" });
97
113
 
98
114
  readonly #skeleton = h("gw-skeleton-text", {
@@ -102,7 +118,7 @@ export class GwChip extends GwElement {
102
118
  readonly #content = h(
103
119
  "span",
104
120
  { class: "content" },
105
- h("slot", { name: "icon" }, this.#icon),
121
+ h("slot", { name: "icon" }, this.#icon, this.#swatch),
106
122
  h("slot"),
107
123
  );
108
124
 
@@ -110,6 +126,7 @@ export class GwChip extends GwElement {
110
126
  super();
111
127
  this.tone = "neutral";
112
128
  this.icon = "";
129
+ this.swatch = "";
113
130
  this.loading = false;
114
131
  this.size = "medium";
115
132
  }
@@ -124,6 +141,8 @@ export class GwChip extends GwElement {
124
141
 
125
142
  setIconPath(this.#icon, this.icon);
126
143
  this.#icon.toggleAttribute("hidden", !this.icon);
144
+ this.#swatch.style.background = this.swatch;
145
+ this.#swatch.hidden = this.swatch === "" || this.icon !== "";
127
146
 
128
147
  setAttribute(this.#chip, "aria-busy", this.loading ? "true" : null);
129
148
  this.#skeleton.hidden = !this.loading;
package/src/layout.ts CHANGED
@@ -36,13 +36,16 @@ export class GwPage extends GwElement {
36
36
  display: flex;
37
37
  flex-direction: column;
38
38
  gap: var(--gw-space-6);
39
- width: 100%;
40
- max-width: var(--gw-content-width);
39
+ /* The same box as gw-app-bar's content, so the two line up */
40
+ width: min(100% - 2 * var(--gw-space-3), var(--gw-content-width));
41
41
  margin-inline: auto;
42
42
  }
43
43
 
44
44
  :host([narrow]) .page {
45
- max-width: var(--gw-content-width-narrow);
45
+ width: min(
46
+ 100% - 2 * var(--gw-space-3),
47
+ var(--gw-content-width-narrow)
48
+ );
46
49
  }
47
50
  `,
48
51
  ];
package/src/styles.ts CHANGED
@@ -48,6 +48,7 @@ export const base = css`
48
48
  box-sizing: border-box;
49
49
  }
50
50
 
51
+ :host([hidden]),
51
52
  [hidden] {
52
53
  display: none !important;
53
54
  }