bref-ui 2.0.0 → 2.1.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 (109) hide show
  1. package/dist/button/button.svelte +67 -47
  2. package/dist/button/types.d.ts +1 -0
  3. package/dist/checkbox/checkbox.svelte +40 -35
  4. package/dist/combobox/combobox.svelte +2 -3
  5. package/dist/combobox/input.svelte +1 -1
  6. package/dist/combobox/list.svelte +5 -5
  7. package/dist/combobox/option.svelte +29 -9
  8. package/dist/dialog/dialog.svelte +15 -12
  9. package/dist/dialog/footer.svelte +2 -2
  10. package/dist/dialog/header.svelte +4 -4
  11. package/dist/icon/icon.svelte +6 -4
  12. package/dist/pill/content.svelte +11 -4
  13. package/dist/pill/content.svelte.d.ts +1 -1
  14. package/dist/pill/on-delete.svelte +42 -13
  15. package/dist/pill/pill.svelte +74 -94
  16. package/dist/pill/registry.json +1 -1
  17. package/dist/pill/types.d.ts +1 -0
  18. package/dist/pill-choice-group/pill-choice-group.svelte +19 -0
  19. package/dist/pill-choice-group/types.d.ts +1 -0
  20. package/dist/pill-group/pill-group.svelte +2 -0
  21. package/dist/pill-group/types.d.ts +1 -0
  22. package/dist/popover/popover.svelte +31 -6
  23. package/dist/popover/types.d.ts +1 -1
  24. package/dist/progress/progress.svelte +42 -55
  25. package/dist/select/empty.svelte +2 -3
  26. package/dist/select/list.svelte +2 -1
  27. package/dist/select/option.svelte +20 -6
  28. package/dist/select/select.svelte +7 -4
  29. package/dist/select/trigger.svelte +30 -7
  30. package/dist/spinner/spinner.svelte +8 -21
  31. package/dist/surface/surface.svelte +6 -1
  32. package/dist/surface/types.d.ts +2 -0
  33. package/dist/switch/switch.svelte +48 -51
  34. package/dist/text-area/registry.json +1 -1
  35. package/dist/text-area/text-area.svelte +33 -73
  36. package/dist/text-area/types.d.ts +1 -0
  37. package/dist/text-input/registry.json +1 -1
  38. package/dist/text-input/text-input.svelte +33 -73
  39. package/dist/text-input/types.d.ts +1 -0
  40. package/dist/theme/cli-theme.svelte +2 -0
  41. package/dist/theme/glass.svelte +60 -0
  42. package/dist/theme/glass.svelte.d.ts +26 -0
  43. package/dist/theme/registry.json +1 -0
  44. package/dist/theme/styles.svelte +6 -0
  45. package/dist/theme/surface.d.ts +1 -1
  46. package/dist/theme/surface.js +3 -2
  47. package/dist/theme/surfaces.svelte +23 -45
  48. package/dist/theme/theme.svelte +2 -0
  49. package/dist/tree-view/tree-node/chevron-button.svelte +9 -9
  50. package/dist/tree-view/tree-node/content.svelte +15 -12
  51. package/dist/tree-view/tree-node/delete-button.svelte +6 -5
  52. package/dist/tree-view/tree-node/tree-node.svelte +23 -24
  53. package/dist/tree-view/tree-view-section.svelte +9 -9
  54. package/dist/tree-view/tree-view.svelte +17 -9
  55. package/dist/tree-view/types.d.ts +2 -1
  56. package/package.json +30 -3
  57. package/readme.md +3 -1
  58. package/src/lib/button/button.svelte +67 -47
  59. package/src/lib/button/types.ts +1 -0
  60. package/src/lib/checkbox/checkbox.svelte +40 -35
  61. package/src/lib/combobox/combobox.svelte +2 -3
  62. package/src/lib/combobox/input.svelte +1 -1
  63. package/src/lib/combobox/list.svelte +5 -5
  64. package/src/lib/combobox/option.svelte +29 -9
  65. package/src/lib/dialog/dialog.svelte +15 -12
  66. package/src/lib/dialog/footer.svelte +2 -2
  67. package/src/lib/dialog/header.svelte +4 -4
  68. package/src/lib/icon/icon.svelte +6 -4
  69. package/src/lib/pill/content.svelte +11 -4
  70. package/src/lib/pill/on-delete.svelte +42 -13
  71. package/src/lib/pill/pill.svelte +74 -94
  72. package/src/lib/pill/registry.json +1 -1
  73. package/src/lib/pill/types.ts +1 -0
  74. package/src/lib/pill-choice-group/pill-choice-group.svelte +19 -0
  75. package/src/lib/pill-choice-group/types.ts +1 -0
  76. package/src/lib/pill-group/pill-group.svelte +2 -0
  77. package/src/lib/pill-group/types.ts +1 -0
  78. package/src/lib/popover/popover.svelte +31 -6
  79. package/src/lib/popover/types.ts +16 -1
  80. package/src/lib/progress/progress.svelte +42 -55
  81. package/src/lib/select/empty.svelte +2 -3
  82. package/src/lib/select/list.svelte +2 -1
  83. package/src/lib/select/option.svelte +20 -6
  84. package/src/lib/select/select.svelte +7 -4
  85. package/src/lib/select/trigger.svelte +30 -7
  86. package/src/lib/spinner/spinner.svelte +8 -21
  87. package/src/lib/surface/surface.svelte +6 -1
  88. package/src/lib/surface/types.ts +2 -0
  89. package/src/lib/switch/switch.svelte +48 -51
  90. package/src/lib/text-area/registry.json +1 -1
  91. package/src/lib/text-area/text-area.svelte +33 -73
  92. package/src/lib/text-area/types.ts +1 -0
  93. package/src/lib/text-input/registry.json +1 -1
  94. package/src/lib/text-input/text-input.svelte +33 -73
  95. package/src/lib/text-input/types.ts +1 -0
  96. package/src/lib/theme/cli-theme.svelte +2 -0
  97. package/src/lib/theme/glass.svelte +60 -0
  98. package/src/lib/theme/registry.json +1 -0
  99. package/src/lib/theme/styles.svelte +6 -0
  100. package/src/lib/theme/surface.ts +4 -2
  101. package/src/lib/theme/surfaces.svelte +23 -45
  102. package/src/lib/theme/theme.svelte +2 -0
  103. package/src/lib/tree-view/tree-node/chevron-button.svelte +9 -9
  104. package/src/lib/tree-view/tree-node/content.svelte +15 -12
  105. package/src/lib/tree-view/tree-node/delete-button.svelte +6 -5
  106. package/src/lib/tree-view/tree-node/tree-node.svelte +23 -24
  107. package/src/lib/tree-view/tree-view-section.svelte +9 -9
  108. package/src/lib/tree-view/tree-view.svelte +17 -9
  109. package/src/lib/tree-view/types.ts +2 -1
@@ -18,7 +18,7 @@
18
18
  >
19
19
  {#if item.icon}<span data-icon><Icon {...item.icon} /></span>{/if}
20
20
  <span data-label>{item.label}</span>
21
- {#if selected}<span data-icon><Icon name="check" /></span>{/if}
21
+ {#if selected}<span data-icon data-check><Icon name="check" /></span>{/if}
22
22
  </button>
23
23
 
24
24
  <style>
@@ -28,15 +28,16 @@
28
28
  align-items: center;
29
29
  gap: 0.6rem;
30
30
  width: 100%;
31
- min-height: var(--internal-height);
31
+ min-height: var(--internal-row-height);
32
32
  padding: 0.375rem 0.65rem;
33
33
  border: 0;
34
- border-radius: 0.4rem;
34
+ border-radius: 0.5rem;
35
35
  background: transparent;
36
36
  color: var(--color-foreground);
37
37
  font: inherit;
38
38
  text-align: left;
39
39
  cursor: pointer;
40
+ transition: background-color 150ms ease;
40
41
  }
41
42
  [data-label] {
42
43
  flex: 1;
@@ -50,13 +51,24 @@
50
51
  flex: 0 0 auto;
51
52
  font-size: var(--internal-icon-size);
52
53
  }
53
- button:hover:not(:disabled),
54
- button[aria-selected='true'],
55
- button[data-active] {
56
- background: color-mix(in srgb, var(--color-primary) 12%, transparent);
54
+ [data-check] {
57
55
  color: var(--color-primary);
58
56
  }
59
- button[data-active],
57
+ button[aria-selected='true'] {
58
+ background: color-mix(in srgb, var(--color-primary) 8%, transparent);
59
+ }
60
+ button:hover:not(:disabled, [data-active]) {
61
+ background: color-mix(in srgb, var(--color-foreground) 6%, transparent);
62
+ }
63
+ button[aria-selected='true']:hover:not(:disabled, [data-active]) {
64
+ background: color-mix(in srgb, var(--color-primary) 14%, transparent);
65
+ }
66
+ button[data-active] {
67
+ background: color-mix(in srgb, var(--color-foreground) 12%, transparent);
68
+ }
69
+ button[aria-selected='true'][data-active] {
70
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
71
+ }
60
72
  button:focus-visible {
61
73
  outline: 2px solid var(--color-primary);
62
74
  outline-offset: -2px;
@@ -65,8 +77,16 @@
65
77
  opacity: 0.5;
66
78
  cursor: not-allowed;
67
79
  }
80
+ @media (prefers-reduced-motion: reduce) {
81
+ button {
82
+ transition: none;
83
+ }
84
+ }
68
85
  @media (forced-colors: active) {
69
- button[data-active],
86
+ button[data-active] {
87
+ outline: 2px solid Highlight;
88
+ outline-offset: -2px;
89
+ }
70
90
  button:focus-visible {
71
91
  outline-color: Highlight;
72
92
  }
@@ -4,6 +4,7 @@
4
4
  import { fly } from 'svelte/transition';
5
5
  import { prefersReducedMotion } from 'svelte/motion';
6
6
  import { MediaQuery } from 'svelte/reactivity';
7
+ import { surface } from '../theme/surface.js';
7
8
  import Header from './header.svelte';
8
9
  import Footer from './footer.svelte';
9
10
  import type { DialogProps } from './types.js';
@@ -71,6 +72,7 @@
71
72
  {#if open}
72
73
  <div
73
74
  data-surface
75
+ class={surface({ color: 'background', glass: true, shadow: 'large' })}
74
76
  use:showModal
75
77
  transition:fly|global={{
76
78
  y: prefersReducedMotion.current ? 0 : 16,
@@ -104,11 +106,7 @@
104
106
  display: flex;
105
107
  flex-direction: column;
106
108
  max-height: calc(100dvh - 2rem);
107
- border: 1px solid color-mix(in srgb, var(--color-foreground, black) 18%, transparent);
108
- border-radius: 0.75rem;
109
- background: var(--color-background, white);
110
- color: var(--color-foreground, black);
111
- box-shadow: 0 1.5rem 4rem color-mix(in srgb, var(--color-foreground, black) 20%, transparent);
109
+ border-radius: 1rem;
112
110
  }
113
111
  dialog[open] {
114
112
  display: flex;
@@ -127,26 +125,30 @@
127
125
  --internal-width: 72rem;
128
126
  }
129
127
  dialog::backdrop {
130
- background: color-mix(in srgb, var(--color-foreground, black) 48%, transparent);
131
- opacity: 0;
128
+ background: color-mix(in srgb, var(--color-background, white) 48%, transparent);
129
+ -webkit-backdrop-filter: blur(0);
132
130
  backdrop-filter: blur(0);
131
+ opacity: 0;
133
132
  transition:
134
133
  opacity 300ms ease,
134
+ -webkit-backdrop-filter 300ms ease,
135
135
  backdrop-filter 300ms ease;
136
136
  }
137
137
  dialog[data-visible='true']::backdrop {
138
- opacity: 1;
138
+ -webkit-backdrop-filter: blur(0.25rem);
139
139
  backdrop-filter: blur(0.25rem);
140
+ opacity: 1;
140
141
  }
141
142
  @starting-style {
142
143
  dialog[data-visible='true']::backdrop {
143
- opacity: 0;
144
+ -webkit-backdrop-filter: blur(0);
144
145
  backdrop-filter: blur(0);
146
+ opacity: 0;
145
147
  }
146
148
  }
147
149
  [data-content] {
148
150
  min-height: 0;
149
- padding: 1.5rem;
151
+ padding: 1.25rem 1.75rem 1.5rem;
150
152
  overflow: auto;
151
153
  }
152
154
  dialog[data-size='full-screen'] {
@@ -172,11 +174,12 @@
172
174
  }
173
175
  @media (forced-colors: active) {
174
176
  [data-surface] {
175
- border-color: CanvasText;
176
- box-shadow: none;
177
+ border: 1px solid CanvasText;
177
178
  }
178
179
  dialog::backdrop {
179
180
  background: Canvas;
181
+ -webkit-backdrop-filter: none;
182
+ backdrop-filter: none;
180
183
  }
181
184
  }
182
185
  </style>
@@ -20,7 +20,7 @@
20
20
  align-items: center;
21
21
  justify-content: flex-end;
22
22
  flex-wrap: wrap;
23
- gap: 0.5rem;
24
- padding: 0.75rem 1.5rem 1.5rem;
23
+ gap: 0.75rem;
24
+ padding: 0 1.75rem 1.5rem;
25
25
  }
26
26
  </style>
@@ -29,7 +29,7 @@
29
29
  display: flex;
30
30
  align-items: flex-start;
31
31
  gap: 1rem;
32
- padding: 1.5rem 1.5rem 0;
32
+ padding: 1.75rem 1.75rem 0;
33
33
  }
34
34
  header > div {
35
35
  flex: 1;
@@ -44,9 +44,9 @@
44
44
  h2 {
45
45
  margin: 0;
46
46
  font: inherit;
47
- font-size: 1.375rem;
48
- font-weight: 600;
49
- line-height: 1.4;
47
+ font-size: 1.5rem;
48
+ font-weight: 400;
49
+ line-height: 1.3;
50
50
  }
51
51
  p {
52
52
  margin: 0.5rem 0 0;
@@ -29,6 +29,8 @@
29
29
  height: 1em;
30
30
  align-items: center;
31
31
  justify-content: center;
32
+ flex-shrink: 0;
33
+ vertical-align: middle;
32
34
  font-family: 'Material Symbols Outlined';
33
35
  font-style: normal;
34
36
  font-weight: 100;
@@ -51,11 +53,11 @@
51
53
  }
52
54
 
53
55
  .x-small {
54
- font-size: 0.875rem;
56
+ font-size: 1rem;
55
57
  }
56
58
 
57
59
  .small {
58
- font-size: 1rem;
60
+ font-size: 1.25rem;
59
61
  }
60
62
 
61
63
  .medium {
@@ -63,11 +65,11 @@
63
65
  }
64
66
 
65
67
  .large {
66
- font-size: 2.5rem;
68
+ font-size: 2rem;
67
69
  }
68
70
 
69
71
  .x-large {
70
- font-size: 5rem;
72
+ font-size: 2.5rem;
71
73
  }
72
74
 
73
75
  .primary {
@@ -3,21 +3,28 @@
3
3
  import Icon from '../icon/icon.svelte';
4
4
  import type { PillProps } from './types.js';
5
5
 
6
- let { label, icon, size }: Pick<PillProps, 'label' | 'icon' | 'size'> = $props();
6
+ let { label, icon }: Pick<PillProps, 'label' | 'icon'> = $props();
7
7
  </script>
8
8
 
9
9
  {#if icon}
10
10
  {#key icon.name}
11
- <span data-icon in:fly>
12
- <Icon {...icon} label={undefined} {size} color={undefined} />
13
- </span>
11
+ <i data-icon in:fly>
12
+ <Icon {...icon} label={undefined} size={undefined} color={undefined} />
13
+ </i>
14
14
  {/key}
15
15
  {/if}
16
16
  <span>{label}</span>
17
17
 
18
18
  <style>
19
19
  [data-icon] {
20
+ all: unset;
20
21
  display: inline-flex;
21
22
  align-items: center;
23
+ flex-shrink: 0;
24
+ font-size: var(--pill-icon-size, 1.125rem);
25
+ }
26
+ span {
27
+ flex: 1;
28
+ text-justify: left;
22
29
  }
23
30
  </style>
@@ -1,26 +1,55 @@
1
1
  <script lang="ts">
2
- import Button from '../button/button.svelte';
2
+ import Icon from '../icon/icon.svelte';
3
3
  import type { PillProps } from './types.js';
4
4
 
5
5
  let { label, onDelete }: Required<Pick<PillProps, 'label' | 'onDelete'>> = $props();
6
6
  </script>
7
7
 
8
- <span data-delete>
9
- <Button
10
- icon={{ name: 'close', label: `Remove ${label}` }}
11
- size="x-small"
12
- rounded
13
- onClick={(event) => {
14
- event.stopPropagation();
15
- onDelete();
16
- }}
17
- />
18
- </span>
8
+ <button
9
+ type="button"
10
+ aria-label={`Remove ${label}`}
11
+ onclick={(event) => {
12
+ event.stopPropagation();
13
+ onDelete();
14
+ }}
15
+ >
16
+ <Icon name="close" />
17
+ </button>
19
18
 
20
19
  <style>
21
- [data-delete] {
20
+ button {
21
+ box-sizing: border-box;
22
22
  position: relative;
23
23
  z-index: 1;
24
24
  display: inline-flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ flex-shrink: 0;
28
+ width: var(--pill-delete-size, 1.5rem);
29
+ height: var(--pill-delete-size, 1.5rem);
30
+ padding: 0;
31
+ border: 0;
32
+ border-radius: 50%;
33
+ background: transparent;
34
+ color: inherit;
35
+ font: inherit;
36
+ font-size: var(--pill-icon-size, 1.125rem);
37
+ cursor: pointer;
38
+ }
39
+ button:hover {
40
+ background: color-mix(in srgb, currentColor 8%, transparent);
41
+ }
42
+ button:active {
43
+ background: color-mix(in srgb, currentColor 16%, transparent);
44
+ }
45
+ button:focus-visible {
46
+ outline: 2px solid currentColor;
47
+ outline-offset: -2px;
48
+ }
49
+ @media (pointer: coarse) {
50
+ button {
51
+ min-width: 44px;
52
+ min-height: 44px;
53
+ }
25
54
  }
26
55
  </style>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  /* eslint-disable max-lines -- Component recipe and animation remain colocated. */
3
3
  import type { PillProps } from './types.js';
4
+ import { surface } from '../theme/surface.js';
4
5
  import Content from './content.svelte';
5
6
  import OnDelete from './on-delete.svelte';
6
7
 
@@ -15,32 +16,46 @@
15
16
  size = 'medium',
16
17
  variant = 'neutral',
17
18
  wide = false,
19
+ class: className,
20
+ glass,
18
21
  ...attributes
19
22
  }: PillProps = $props();
23
+
24
+ const nativeAttributes = $derived({
25
+ ...attributes,
26
+ onclick: onClick,
27
+ role: onClick ? 'button' : attributes.role,
28
+ tabindex: onClick ? (attributes.tabindex ?? 0) : attributes.tabindex,
29
+ 'aria-label': attributes['aria-labelledby']
30
+ ? undefined
31
+ : (attributes['aria-label'] ?? (onClick ? label : undefined)),
32
+ onkeydown: onClick
33
+ ? (event: KeyboardEvent & { currentTarget: EventTarget & HTMLDivElement }) => {
34
+ attributes.onkeydown?.(event);
35
+ if (
36
+ !event.defaultPrevented &&
37
+ event.target === event.currentTarget &&
38
+ (event.key === 'Enter' || event.key === ' ')
39
+ ) {
40
+ event.preventDefault();
41
+ if (!event.repeat) event.currentTarget.click();
42
+ }
43
+ }
44
+ : attributes.onkeydown
45
+ });
20
46
  </script>
21
47
 
22
- <!-- svelte-ignore a11y_no_noninteractive_tabindex (The button role and tab stop are enabled together.) -->
23
48
  <div
24
- {...attributes}
25
- onclick={onClick}
26
- role={onClick ? 'button' : attributes.role}
27
- tabindex={onClick ? (attributes.tabindex ?? 0) : attributes.tabindex}
28
- aria-label={attributes['aria-labelledby']
29
- ? undefined
30
- : (attributes['aria-label'] ?? (onClick ? label : undefined))}
31
- onkeydown={onClick
32
- ? (event) => {
33
- attributes.onkeydown?.(event);
34
- if (
35
- !event.defaultPrevented &&
36
- event.target === event.currentTarget &&
37
- (event.key === 'Enter' || event.key === ' ')
38
- ) {
39
- event.preventDefault();
40
- if (!event.repeat) event.currentTarget.click();
41
- }
42
- }
43
- : attributes.onkeydown}
49
+ {...nativeAttributes}
50
+ class={[
51
+ surface({
52
+ color,
53
+ variant,
54
+ glass,
55
+ hover: onClick || onDelete ? 'small' : undefined
56
+ }),
57
+ className
58
+ ]}
44
59
  data-swoosh={swoosh || undefined}
45
60
  data-clickable={onClick ? '' : undefined}
46
61
  data-icon={icon ? '' : undefined}
@@ -48,85 +63,69 @@
48
63
  data-color={color}
49
64
  data-size={size}
50
65
  data-variant={variant}
51
- style:--pill-color={`var(--color-${color}, var(--color-foreground))`}
52
66
  data-wide={wide}
53
67
  >
54
- <Content {label} {icon} {size} />
68
+ <Content {label} {icon} />
55
69
  {@render children?.()}
70
+ <!-- todo: the on delete should always be at right msot of the pill, content has to "fill the parent" -->
56
71
  {#if onDelete}<OnDelete {label} {onDelete} />{/if}
57
72
  </div>
58
73
 
59
74
  <style>
60
75
  div {
61
- --pill-tint: 3%;
62
- --pill-background: color-mix(
63
- in srgb,
64
- color-mix(in srgb, var(--pill-color) var(--pill-tint), var(--color-background, white)) 85%,
65
- transparent
66
- );
67
- --pill-border: color-mix(in srgb, var(--pill-color) 20%, transparent);
68
- --pill-highlight: color-mix(in srgb, var(--color-foreground, black) 16%, transparent);
69
- --pill-content: var(--pill-color);
70
- --pill-padding: 1.125rem;
71
- --pill-height: 3rem;
76
+ --pill-glint: color-mix(in srgb, var(--color-foreground, black) 8%, transparent);
77
+ --pill-padding: 0.75rem;
78
+ --pill-height: 2rem;
79
+ --pill-icon-size: 1.125rem;
80
+ --pill-delete-size: 1.5rem;
72
81
  box-sizing: border-box;
73
82
  display: inline-flex;
74
83
  position: relative;
75
84
  isolation: isolate;
76
85
  align-items: center;
77
- gap: 0.5rem;
78
- justify-content: center;
86
+ gap: 0.375rem;
87
+ justify-content: flex-start;
79
88
  min-width: 0;
89
+ width: fit-content;
80
90
  max-width: 100%;
81
91
  min-height: var(--pill-height);
82
- padding: 0.25rem var(--pill-padding);
83
- border: 1px solid var(--pill-border);
92
+ padding: 0.125rem var(--pill-padding);
84
93
  border-radius: 999px;
85
- background: var(--pill-background);
86
- box-shadow:
87
- inset 0 1px 0 var(--pill-highlight),
88
- 0 2px 6px color-mix(in srgb, var(--color-foreground, black) 8%, transparent);
89
- -webkit-backdrop-filter: blur(0.5rem) saturate(120%);
90
- backdrop-filter: blur(0.5rem) saturate(120%);
91
- transition: all 150ms;
92
- color: var(--pill-content);
93
94
  font: inherit;
94
95
  line-height: 1.25;
95
- text-align: center;
96
+ text-align: left;
96
97
  white-space: normal;
97
98
  overflow-wrap: anywhere;
98
99
  overflow: hidden;
99
100
  user-select: none;
100
101
  }
101
102
  div[data-size='x-small'] {
102
- --pill-height: 1.75rem;
103
+ --pill-height: 1.5rem;
103
104
  --pill-padding: 0.5rem;
104
- font-size: 0.8rem;
105
+ --pill-icon-size: 0.875rem;
106
+ font-size: 0.8125rem;
105
107
  }
106
108
  div[data-size='small'] {
107
- --pill-height: 2.5rem;
108
- --pill-padding: 0.875rem;
109
+ --pill-height: 1.75rem;
110
+ --pill-padding: 0.625rem;
111
+ --pill-icon-size: 1rem;
112
+ font-size: 0.875rem;
109
113
  }
110
114
  div[data-size='large'] {
111
- --pill-height: 4rem;
112
- --pill-padding: 1.5rem;
113
- font-size: 1.25rem;
115
+ --pill-height: 2.25rem;
116
+ --pill-padding: 0.875rem;
117
+ --pill-icon-size: 1.25rem;
118
+ --pill-delete-size: 1.75rem;
114
119
  }
115
120
  div[data-size='x-large'] {
116
- --pill-height: 5rem;
117
- --pill-padding: 2rem;
118
- font-size: 1.5rem;
119
- }
120
- div[data-variant='soft'] {
121
- --pill-tint: 16%;
122
- --pill-border: color-mix(in srgb, var(--pill-color) 30%, transparent);
123
- --pill-content: color-mix(in srgb, var(--pill-color) 80%, var(--color-foreground, black));
121
+ --pill-height: 2.5rem;
122
+ --pill-padding: 1rem;
123
+ --pill-icon-size: 1.375rem;
124
+ --pill-delete-size: 2rem;
125
+ font-size: 1.125rem;
124
126
  }
125
127
  div[data-variant='filled'] {
126
- --pill-tint: 88%;
127
- --pill-border: color-mix(in srgb, var(--pill-color) 68%, var(--color-foreground, black));
128
- --pill-highlight: color-mix(in srgb, var(--color-foreground, black) 28%, transparent);
129
- --pill-content: color-mix(in srgb, var(--color-background, white) 90%, var(--pill-color));
128
+ --pill-glint: color-mix(in srgb, var(--surface-color) 28%, transparent);
130
129
  }
131
130
  div[data-icon] {
132
131
  padding-left: calc(var(--pill-padding) * 0.6);
@@ -134,12 +133,8 @@
134
133
  div[data-removable] {
135
134
  padding-right: calc(var(--pill-padding) / 2);
136
135
  }
137
- div[data-clickable] {
138
- cursor: pointer;
139
- }
140
- div[data-clickable]:focus-visible {
141
- outline: 2px solid var(--color-primary, currentColor);
142
- outline-offset: 3px;
136
+ div[data-wide='true'] {
137
+ width: 100%;
143
138
  }
144
139
  div[data-swoosh]::before {
145
140
  position: absolute;
@@ -148,21 +143,13 @@
148
143
  inset-inline-start: -45%;
149
144
  width: 40%;
150
145
  content: '';
151
- background: linear-gradient(
152
- 90deg,
153
- transparent,
154
- color-mix(in srgb, var(--pill-color) 28%, transparent),
155
- transparent
156
- );
146
+ background: linear-gradient(90deg, transparent, var(--pill-glint), transparent);
157
147
  filter: blur(0.3rem);
158
148
  opacity: 0;
159
149
  pointer-events: none;
160
150
  transform: skewX(-18deg);
161
151
  animation: pill-swoosh 2s ease-in-out infinite;
162
152
  }
163
- div[data-wide='true'] {
164
- width: 100%;
165
- }
166
153
  @keyframes pill-swoosh {
167
154
  0%,
168
155
  20% {
@@ -179,22 +166,15 @@
179
166
  transform: translateX(400%) skewX(-18deg);
180
167
  }
181
168
  }
182
- @media (prefers-reduced-motion: reduce) {
183
- div {
184
- transition: none;
169
+ @media (pointer: coarse) {
170
+ div[data-clickable] {
171
+ min-width: 44px;
172
+ min-height: 44px;
185
173
  }
174
+ }
175
+ @media (prefers-reduced-motion: reduce) {
186
176
  div[data-swoosh]::before {
187
177
  animation: none;
188
178
  }
189
179
  }
190
- @media (forced-colors: active) {
191
- div {
192
- border-color: CanvasText;
193
- color: CanvasText;
194
- background: Canvas;
195
- box-shadow: none;
196
- -webkit-backdrop-filter: none;
197
- backdrop-filter: none;
198
- }
199
- }
200
180
  </style>
@@ -2,5 +2,5 @@
2
2
  "id": "pill",
3
3
  "files": ["pill.svelte", "content.svelte", "on-delete.svelte"],
4
4
  "exports": { "Pill": "pill.svelte" },
5
- "dependencies": ["theme", "icon", "button"]
5
+ "dependencies": ["theme", "icon"]
6
6
  }
@@ -19,4 +19,5 @@ export type PillProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'color
19
19
  onDelete?: () => void;
20
20
  swoosh?: boolean;
21
21
  wide?: boolean;
22
+ glass?: boolean;
22
23
  };
@@ -12,6 +12,7 @@
12
12
  size = 'medium',
13
13
  variant = 'neutral',
14
14
  color = 'foreground',
15
+ glass,
15
16
  animateSelected = false,
16
17
  ...attributes
17
18
  }: PillChoiceGroupProps = $props();
@@ -80,6 +81,7 @@
80
81
  label={item.label}
81
82
  icon={item.icon}
82
83
  {color}
84
+ {glass}
83
85
  swoosh={selected && animateSelected}
84
86
  variant={selected ? selectedVariant : variant}
85
87
  {size}
@@ -110,6 +112,15 @@
110
112
  clip-path: inset(50%);
111
113
  overflow: hidden;
112
114
  }
115
+ div:has(input:checked) {
116
+ --pill-choice-emphasis: 18%;
117
+ }
118
+ div:not([data-disabled]):is(:hover, :has(input:focus-visible)) {
119
+ --pill-choice-hover: 2%;
120
+ }
121
+ div:not([data-disabled]):active {
122
+ --pill-choice-hover: 4%;
123
+ }
113
124
  div:has(input:focus-visible) {
114
125
  outline: 2px solid var(--color-primary, currentColor);
115
126
  outline-offset: 3px;
@@ -122,6 +133,14 @@
122
133
  opacity: 0.45;
123
134
  cursor: not-allowed;
124
135
  }
136
+ @media (pointer: coarse) {
137
+ div {
138
+ display: inline-grid;
139
+ min-width: 44px;
140
+ min-height: 44px;
141
+ align-items: stretch;
142
+ }
143
+ }
125
144
  @media (forced-colors: active) {
126
145
  div:has(input:checked) {
127
146
  outline: 2px solid Highlight;
@@ -10,5 +10,6 @@ export type PillChoiceGroupProps = Omit<HTMLAttributes<HTMLUListElement>, 'child
10
10
  size?: Size;
11
11
  variant?: Exclude<Variant, 'filled'>;
12
12
  color?: PillProps['color'];
13
+ glass?: PillProps['glass'];
13
14
  animateSelected?: boolean;
14
15
  };
@@ -9,6 +9,7 @@
9
9
  color = 'foreground',
10
10
  size = 'medium',
11
11
  variant = 'neutral',
12
+ glass,
12
13
  ...attributes
13
14
  }: PillGroupProps = $props();
14
15
  </script>
@@ -21,6 +22,7 @@
21
22
  {color}
22
23
  {size}
23
24
  {variant}
25
+ {glass}
24
26
  onDelete={onDelete ? () => onDelete?.(item.id) : undefined}
25
27
  />
26
28
  {/snippet}
@@ -8,6 +8,7 @@ export type PillGroupProps = Omit<HTMLAttributes<HTMLUListElement>, 'children' |
8
8
  color?: PillProps['color'];
9
9
  size?: PillProps['size'];
10
10
  variant?: PillProps['variant'];
11
+ glass?: PillProps['glass'];
11
12
  };
12
13
  export type PillGroupContainerProps = Omit<HTMLAttributes<HTMLUListElement>, 'children'> &
13
14
  Pick<PillGroupProps, 'items' | 'size'> & {