bref-ui 2.0.1 → 2.1.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.
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 +1 -1
  57. package/readme.md +54 -13
  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
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { prefersReducedMotion } from 'svelte/motion';
3
3
  import { fly } from 'svelte/transition';
4
+ import { surface } from '../../theme/surface.js';
4
5
  import ChevronButton from './chevron-button.svelte';
5
6
  import DeleteButton from './delete-button.svelte';
6
7
  import Content from './content.svelte';
@@ -11,6 +12,7 @@
11
12
  item,
12
13
  selection = $bindable(),
13
14
  defaultExpanded = false,
15
+ glass = false,
14
16
  onDelete,
15
17
  indent = 0
16
18
  }: TreeNodeProps = $props();
@@ -32,10 +34,12 @@
32
34
  </script>
33
35
 
34
36
  <div
35
- in:fly|global={{ y: -4, duration: prefersReducedMotion.current ? 0 : 150 }}
37
+ class={selected ? surface({ variant: 'soft', color: 'primary', glass }) : undefined}
38
+ in:fly|global={{ x: -8, duration: prefersReducedMotion.current ? 0 : 200 }}
39
+ out:fly|global={{ x: -8, duration: prefersReducedMotion.current ? 0 : 150 }}
36
40
  data-selected={selected}
37
41
  data-disabled={item.disabled}
38
- style:padding-left={`calc(0.25rem + ${indent * 1.25}rem)`}
42
+ style:padding-left={`calc(0.25rem + ${indent} * var(--tree-action-size))`}
39
43
  >
40
44
  {#if item.children?.length}
41
45
  <ChevronButton
@@ -56,18 +60,20 @@
56
60
  />
57
61
  {/if}
58
62
  </div>
59
- {#if item.children?.length}
60
- <div role="group" id={childrenId} hidden={!expanded}>
61
- {#each item.children as child (child.id)}
62
- <RecursiveNode indent={indent + 1} item={child} bind:selection {defaultExpanded} {onDelete} />
63
- {/each}
64
- </div>
63
+ {#if item.children?.length && expanded}
64
+ {#each item.children as child (child.id)}
65
+ <RecursiveNode
66
+ indent={indent + 1}
67
+ item={child}
68
+ bind:selection
69
+ {defaultExpanded}
70
+ {onDelete}
71
+ {glass}
72
+ />
73
+ {/each}
65
74
  {/if}
66
75
 
67
76
  <style>
68
- * {
69
- transition: all 150ms;
70
- }
71
77
  div[data-selected] {
72
78
  box-sizing: border-box;
73
79
  position: relative;
@@ -79,33 +85,26 @@
79
85
  border: 1px solid transparent;
80
86
  border-radius: 999px;
81
87
  overflow-wrap: anywhere;
88
+ transition: all 150ms ease;
82
89
  }
83
90
  [data-spacer] {
84
91
  flex: 0 0 var(--tree-action-size);
85
92
  }
86
- div[role='group'] {
87
- display: contents;
88
- }
89
- div[hidden] {
90
- display: none;
91
- }
92
93
  div[data-selected='true'] {
93
- border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
94
- background: color-mix(in srgb, var(--color-primary) 16%, transparent);
95
- color: var(--color-primary);
94
+ --tree-action-color: var(--color-primary);
96
95
  }
97
96
  div[data-selected='false']:not([data-disabled='true']):hover {
98
- background: color-mix(in srgb, var(--color-primary) 12%, transparent);
97
+ background: color-mix(in srgb, var(--color-foreground) 5%, transparent);
99
98
  }
100
99
  @media (prefers-reduced-motion: reduce) {
101
- * {
100
+ div[data-selected] {
102
101
  transition: none;
103
102
  }
104
103
  }
105
104
  @media (forced-colors: active) {
106
105
  div[data-selected='true'] {
107
- border-color: Highlight;
108
- color: Highlight;
106
+ outline: 1px solid Highlight;
107
+ outline-offset: -1px;
109
108
  }
110
109
  }
111
110
  </style>
@@ -18,13 +18,10 @@
18
18
  display: flex;
19
19
  flex-direction: column;
20
20
  align-items: flex-start;
21
- gap: 0.25rem;
22
- padding: 0.75rem 0.5rem 0.25rem;
21
+ gap: 0.375rem;
22
+ padding: 1rem 0.75rem 0.75rem;
23
23
  color: var(--color-muted);
24
- font-size: 0.8em;
25
- font-weight: 600;
26
- letter-spacing: 0.06em;
27
- text-transform: uppercase;
24
+ line-height: 1.4;
28
25
  overflow-wrap: anywhere;
29
26
  }
30
27
  div {
@@ -32,11 +29,14 @@
32
29
  align-items: center;
33
30
  gap: 0.5rem;
34
31
  min-width: 0;
32
+ color: var(--color-foreground);
33
+ font-size: 0.875em;
34
+ font-weight: 500;
35
+ letter-spacing: 0.06em;
36
+ text-transform: uppercase;
35
37
  }
36
38
  p {
37
39
  margin: 0;
38
- font-weight: 400;
39
- letter-spacing: normal;
40
- text-transform: none;
40
+ font-size: 0.75em;
41
41
  }
42
42
  </style>
@@ -8,6 +8,7 @@
8
8
  sections = [],
9
9
  label = 'Tree view',
10
10
  size = 'medium',
11
+ glass = false,
11
12
  selection = $bindable(),
12
13
  defaultExpanded = false,
13
14
  onDelete,
@@ -21,7 +22,7 @@
21
22
  {#if node}
22
23
  {@render node(item)}
23
24
  {:else}
24
- <TreeNode {item} bind:selection {defaultExpanded} {onDelete} />
25
+ <TreeNode {item} bind:selection {defaultExpanded} {onDelete} {glass} />
25
26
  {/if}
26
27
  {/each}
27
28
  </div>
@@ -38,28 +39,35 @@
38
39
  </div>
39
40
 
40
41
  <style>
41
- div {
42
- --tree-height: 2.75rem;
43
- --tree-action-size: 2.25rem;
42
+ div[role='group'] {
43
+ --tree-height: 2rem;
44
+ --tree-action-size: 1.5rem;
44
45
  display: grid;
45
46
  gap: 0.375rem;
46
47
  min-width: 0;
47
48
  color: var(--color-foreground);
48
49
  font: inherit;
50
+ line-height: 1.25;
49
51
  }
50
52
  div[data-size='small'] {
51
- --tree-height: 2.5rem;
52
- --tree-action-size: 2rem;
53
+ --tree-height: 1.75rem;
53
54
  font-size: 0.875rem;
54
55
  }
55
56
  div[data-size='large'] {
56
- --tree-height: 3.25rem;
57
- --tree-action-size: 2.5rem;
57
+ --tree-height: 2.5rem;
58
+ --tree-action-size: 2rem;
58
59
  font-size: 1.125rem;
59
60
  }
60
61
  div[data-nodes] {
61
62
  display: flex;
62
63
  flex-direction: column;
63
- gap: 0.25rem;
64
+ gap: 0.125rem;
65
+ min-width: 0;
66
+ }
67
+ @media (pointer: coarse) {
68
+ div[role='group'] {
69
+ --tree-height: 2.75rem;
70
+ --tree-action-size: 2.75rem;
71
+ }
64
72
  }
65
73
  </style>
@@ -17,12 +17,13 @@ export interface TreeViewProps {
17
17
  sections?: TreeSectionProps[];
18
18
  label?: string;
19
19
  size?: BaseSize;
20
+ glass?: boolean;
20
21
  selection?: string | string[];
21
22
  defaultExpanded?: boolean;
22
23
  onDelete?: (id: string) => void;
23
24
  node?: Snippet<[TreeItemProps]>;
24
25
  }
25
- export interface TreeNodeProps extends Pick<TreeViewProps, 'selection' | 'defaultExpanded' | 'onDelete'> {
26
+ export interface TreeNodeProps extends Pick<TreeViewProps, 'selection' | 'defaultExpanded' | 'onDelete' | 'glass'> {
26
27
  item: TreeItemProps;
27
28
  indent?: number;
28
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bref-ui",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "A Svelte-first, human-first, agent-first UI library for building beautiful sites and apps with components you can make your own.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/readme.md CHANGED
@@ -30,22 +30,19 @@ props and theme customization on `/button`, `/icon` and `/theme`.
30
30
 
31
31
  ### Use the local package
32
32
 
33
- The CLI/registry changes are currently local and uncommitted, so they are absent
34
- from the clone above. This section and the copy workflow below require the
35
- development checkout containing those changes.
36
-
37
- In that checkout, build a local package:
33
+ The package includes the component CLI and canonical sources. Build a local package
34
+ from the development checkout:
38
35
 
39
36
  ```sh
40
37
  bun run build
41
38
  npm pack --ignore-scripts
42
39
  ```
43
40
 
44
- This produces `bref-ui-2.0.0.tgz`. In an existing Svelte 5.56.1+ project with its
41
+ This produces `bref-ui-2.1.1.tgz`. In an existing Svelte 5.56.1+ project with its
45
42
  dependencies installed, install that tarball, replacing the example path:
46
43
 
47
44
  ```sh
48
- npm install /path/to/bref-ui-2.0.0.tgz
45
+ npm install /path/to/bref-ui-2.1.1.tgz
49
46
  ```
50
47
 
51
48
  Then use the components in a `.svelte` file:
@@ -86,7 +83,9 @@ Button use the same paint recipe; apply it to native HTML with the typed helper:
86
83
 
87
84
  All options are optional: variant defaults to neutral and color to foreground;
88
85
  shadow and hover accept small, medium or large. Shadow also accepts true for medium
89
- and false for none. SurfaceProps extends SurfaceBaseProps with component props.
86
+ and false for none. Set `glass: true` for translucent paint and backdrop blur, or use
87
+ `<Surface glass>` and `<Button glass>`; glass defaults to false.
88
+ SurfaceProps extends SurfaceBaseProps with component props.
90
89
  Supply padding, radius and layout
91
90
  in element styles. The helper adds classes only; Theme loads their CSS. SurfaceBaseProps
92
91
  is available from `bref-ui/types`. Copied components include the helper in
@@ -109,13 +108,55 @@ is available from `bref-ui/types`. Copied components include the helper in
109
108
  complete site generation, generative UI and interactive interfaces for agent
110
109
  workflows.
111
110
 
112
- ## Copy components into a project (WIP)
111
+ ## Copy components into a project
112
+
113
+ The package includes the `bref-ui` executable, which requires Bun. After installing
114
+ the tarball, run commands from the consumer project's root:
115
+
116
+ ```sh
117
+ bun run bref-ui init
118
+ bun run bref-ui add button
119
+ bun run bref-ui --help
120
+ ```
121
+
122
+ `init` creates `bref.config.json` and leaves an existing file untouched:
123
+
124
+ ```json
125
+ {
126
+ "ui": "$lib/ui"
127
+ }
128
+ ```
129
+
130
+ The default destination is `src/lib/ui`. Edit `ui` to another `$lib` path, such as
131
+ `$lib/bref`, to change the destination. The CLI maps `$lib` to `src/lib`; it does not
132
+ discover project aliases or accept an `--alias` option. Configure the same alias
133
+ in a plain Svelte project if using `$lib/ui` imports.
134
+
135
+ `add <component>` copies the component and its registry dependencies, then merges
136
+ component exports into the destination's `index.ts`. Button includes Icon and Theme.
137
+ The preview lists `add`, `unchanged` and `conflict` files. Each conflicting file
138
+ requires overwrite approval, followed by a final installation confirmation. Declining
139
+ a prompt cancels without writes; identical files are skipped.
140
+
141
+ Use the copied components in a `.svelte` file:
142
+
143
+ ```svelte
144
+ <script lang="ts">
145
+ import { Button, Theme } from '$lib/ui';
146
+ </script>
147
+
148
+ <Theme />
149
+ <Button label="Save" />
150
+ ```
113
151
 
114
- The local package includes the `bref` executable. After installing the tarball,
115
- preview and accept the setup and Button copies:
152
+ Keep `bref-ui` installed for shared types imported from `bref-ui/types`. To copy
153
+ Theme separately, run `bun run bref-ui add theme`. The copied Theme uses the default
154
+ palette and has no `palette` prop; customize its copied CSS tokens. The package
155
+ Theme retains its `palette` prop.
116
156
 
117
- Run `bun run bref-ui add theme` to copy the default-only Theme source into the configured UI directory. The copied `Theme` has no `palette` prop; edit its CSS tokens directly. The package `Theme` retains the `palette` prop. Button copies include Icon and required types. SvelteKit can use
118
- `--alias '$lib/bref'` during init after its existing sync command.
157
+ Available component IDs: `button`, `checkbox`, `combobox`, `dialog`, `icon`, `pill`,
158
+ `pill-choice-group`, `pill-group`, `popover`, `progress`, `select`, `spinner`,
159
+ `surface`, `switch`, `text-area`, `text-input`, `theme` and `tree-view`.
119
160
 
120
161
  ## Documentation and demos
121
162
 
@@ -3,12 +3,13 @@
3
3
  import Icon from '../icon/icon.svelte';
4
4
  import { surface } from '../theme/surface.js';
5
5
  import type { ButtonProps } from './types.js';
6
- import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
6
+ import type { HTMLButtonAttributes } from 'svelte/elements';
7
7
 
8
8
  let {
9
9
  color = 'foreground',
10
10
  size = 'medium',
11
11
  variant = 'neutral',
12
+ glass = false,
12
13
  label,
13
14
  icon,
14
15
  trailingIcon,
@@ -22,9 +23,8 @@
22
23
  }: ButtonProps = $props();
23
24
 
24
25
  const isIcon = $derived(label === undefined);
25
- const isLink = $derived(href !== undefined);
26
26
  const common = $derived({
27
- class: ['button', surface({ variant, color, shadow: 'small', hover: 'medium' }), className],
27
+ class: ['button', surface({ variant, color, glass, hover: 'medium' }), className],
28
28
  'aria-label': attributes['aria-label'] ?? (isIcon ? (icon?.label ?? icon?.name) : undefined),
29
29
  'data-color': color,
30
30
  'data-size': size,
@@ -33,9 +33,24 @@
33
33
  'data-wide': wide && !isIcon ? '' : undefined,
34
34
  'data-rounded': rounded && isIcon ? '' : undefined
35
35
  });
36
+ const native = $derived(
37
+ href !== undefined
38
+ ? {
39
+ href: disabled ? undefined : href,
40
+ 'aria-disabled': disabled ? true : attributes['aria-disabled'],
41
+ tabindex: disabled ? -1 : attributes.tabindex
42
+ }
43
+ : { type: (attributes as HTMLButtonAttributes).type ?? 'button', disabled }
44
+ );
36
45
  </script>
37
46
 
38
- {#snippet content()}
47
+ <svelte:element
48
+ this={href !== undefined ? 'a' : 'button'}
49
+ {...attributes}
50
+ {...common}
51
+ {...native}
52
+ onclick={disabled && href !== undefined ? (event: MouseEvent) => event.preventDefault() : onClick}
53
+ >
39
54
  {#if icon}
40
55
  <span><Icon {...icon} color={undefined} label={undefined} /></span>
41
56
  {/if}
@@ -45,77 +60,64 @@
45
60
  <span><Icon {...trailingIcon} color={undefined} label={undefined} /></span>
46
61
  {/if}
47
62
  {/if}
48
- {/snippet}
49
-
50
- {#if isLink}
51
- <!-- eslint-disable svelte/no-navigation-without-resolve -- Native library links also support consumers outside SvelteKit. -->
52
- <a
53
- {...attributes as HTMLAnchorAttributes}
54
- {...common}
55
- href={disabled ? undefined : href}
56
- aria-disabled={disabled ? true : attributes['aria-disabled']}
57
- tabindex={disabled ? -1 : attributes.tabindex}
58
- onclick={disabled ? (event) => event.preventDefault() : onClick}
59
- >
60
- {@render content()}
61
- </a>
62
- <!-- eslint-enable svelte/no-navigation-without-resolve -->
63
- {:else}
64
- <button
65
- {...attributes as HTMLButtonAttributes}
66
- {...common}
67
- type={(attributes as HTMLButtonAttributes).type ?? 'button'}
68
- {disabled}
69
- onclick={onClick}
70
- >
71
- {@render content()}
72
- </button>
73
- {/if}
63
+ </svelte:element>
74
64
 
75
65
  <style>
76
66
  .button {
77
- --internal-height: max(48px, 3rem);
78
- --internal-padding: 1.125rem;
79
- --internal-icon-size: calc(var(--internal-height) / 2 + 0.25rem);
67
+ --internal-height: max(40px, 2.5rem);
68
+ --internal-target: 24px;
69
+ --internal-padding: 1rem;
70
+ --internal-icon-size: 1.25rem;
71
+ --internal-font-size: 1rem;
80
72
  box-sizing: border-box;
81
73
  display: inline-flex;
82
74
  align-items: center;
83
75
  justify-content: center;
84
- text-transform: capitalize;
85
76
  gap: 0.5rem;
86
- min-height: var(--internal-height);
87
- min-width: 24px;
77
+ min-height: max(var(--internal-height), var(--internal-target));
78
+ min-width: var(--internal-target);
88
79
  padding: 0.25rem var(--internal-padding);
89
- border-radius: 0.45rem;
80
+ border-radius: 0.625rem;
90
81
  font: inherit;
91
- font-size: 1rem;
82
+ font-size: var(--internal-font-size);
83
+ line-height: 1.25;
92
84
  text-decoration: none;
93
85
  }
94
86
  .button[data-size='x-small'] {
95
87
  --internal-height: max(28px, 1.75rem);
96
- --internal-padding: 0.5rem;
88
+ --internal-padding: 0.625rem;
89
+ --internal-icon-size: 1rem;
90
+ --internal-font-size: 0.8125rem;
97
91
  }
98
92
  .button[data-size='small'] {
99
- --internal-height: max(40px, 2.5rem);
100
- --internal-padding: 0.875rem;
93
+ --internal-height: max(32px, 2rem);
94
+ --internal-padding: 0.75rem;
95
+ --internal-icon-size: 1.125rem;
96
+ --internal-font-size: 0.875rem;
101
97
  }
102
98
  .button[data-size='large'] {
103
- --internal-height: max(64px, 4rem);
104
- --internal-padding: 1.5rem;
99
+ --internal-height: max(48px, 3rem);
100
+ --internal-padding: 1.25rem;
101
+ --internal-icon-size: 1.5rem;
105
102
  }
106
103
  .button[data-size='x-large'] {
107
- --internal-height: max(80px, 5rem);
108
- --internal-padding: 2rem;
104
+ --internal-height: max(56px, 3.5rem);
105
+ --internal-padding: 1.5rem;
106
+ --internal-icon-size: 1.75rem;
107
+ --internal-font-size: 1.125rem;
109
108
  }
110
109
  .button[data-kind='icon'] {
111
- width: var(--internal-height);
112
- height: var(--internal-height);
110
+ width: max(var(--internal-height), var(--internal-target));
111
+ height: max(var(--internal-height), var(--internal-target));
113
112
  padding: 0;
114
113
  font-size: var(--internal-icon-size);
115
114
  }
116
115
  span {
117
116
  display: inline-flex;
117
+ align-items: center;
118
+ flex-shrink: 0;
118
119
  font-size: var(--internal-icon-size);
120
+ line-height: 1;
119
121
  }
120
122
  .button[data-wide] {
121
123
  width: 100%;
@@ -123,4 +125,22 @@
123
125
  .button[data-rounded] {
124
126
  border-radius: 50%;
125
127
  }
128
+ .button:is(:disabled, [aria-disabled='true']) {
129
+ --surface-color: var(--color-muted, gray);
130
+ }
131
+ .button[data-variant='filled']:not(:disabled, [aria-disabled='true']):is(:hover, :focus-visible) {
132
+ filter: brightness(1.1);
133
+ }
134
+ .button[data-variant='filled']:not(:disabled, [aria-disabled='true']):is(
135
+ :active,
136
+ [aria-pressed='true'],
137
+ [data-surface-pressed='true']
138
+ ) {
139
+ filter: brightness(0.85);
140
+ }
141
+ @media (pointer: coarse) {
142
+ .button {
143
+ --internal-target: 44px;
144
+ }
145
+ }
126
146
  </style>
@@ -35,6 +35,7 @@ export type ButtonProps = {
35
35
  color?: Exclude<Color, 'background'>;
36
36
  size?: Size;
37
37
  variant?: Variant;
38
+ glass?: boolean;
38
39
  disabled?: boolean;
39
40
  onClick?: DOMAttributes<HTMLButtonElement | HTMLAnchorElement>['onclick'];
40
41
  } & Omit<DOMAttributes<HTMLButtonElement | HTMLAnchorElement>, 'children' | 'onclick'> &
@@ -21,12 +21,24 @@
21
21
  --internal-size: 1.25rem;
22
22
  --internal-tint: var(--color-foreground, black);
23
23
  --internal-strength-light: 4%;
24
- --internal-strength-dark: 16%;
25
- --internal-border: light-dark(
26
- color-mix(in srgb, var(--color-foreground, black) 12%, transparent),
27
- color-mix(in srgb, var(--color-foreground, black) 30%, transparent)
24
+ --internal-strength-dark: 8%;
25
+ --internal-fill: light-dark(
26
+ color-mix(
27
+ in srgb,
28
+ var(--internal-tint) var(--internal-strength-light),
29
+ var(--color-background, white)
30
+ ),
31
+ color-mix(
32
+ in srgb,
33
+ var(--internal-tint) var(--internal-strength-dark),
34
+ var(--color-background, white)
35
+ )
36
+ );
37
+ --internal-border: color-mix(
38
+ in srgb,
39
+ var(--color-foreground, black) 48%,
40
+ var(--color-background, white)
28
41
  );
29
- --internal-highlight: color-mix(in srgb, var(--color-foreground, black) 12%, transparent);
30
42
  box-sizing: border-box;
31
43
  position: relative;
32
44
  display: inline-flex;
@@ -37,30 +49,14 @@
37
49
  block-size: var(--internal-size);
38
50
  border: 1px solid var(--internal-border);
39
51
  border-radius: calc(var(--internal-size) * 0.2);
40
- background: color-mix(
41
- in srgb,
42
- light-dark(
43
- color-mix(
44
- in srgb,
45
- var(--internal-tint) var(--internal-strength-light),
46
- var(--color-background, white)
47
- ),
48
- color-mix(
49
- in srgb,
50
- var(--internal-tint) var(--internal-strength-dark),
51
- var(--color-background, white)
52
- )
53
- )
54
- 85%,
55
- transparent
56
- );
57
- box-shadow: inset 0 1px 0 var(--internal-highlight);
52
+ background: var(--internal-fill);
58
53
  color: var(--color-foreground, black);
59
54
  font-size: calc(var(--internal-size) * 0.8);
60
55
  vertical-align: middle;
61
- transition: all 150ms ease;
62
- -webkit-backdrop-filter: blur(0.5rem) saturate(120%);
63
- backdrop-filter: blur(0.5rem) saturate(120%);
56
+ transition:
57
+ background-color 150ms ease,
58
+ border-color 150ms ease,
59
+ color 150ms ease;
64
60
  }
65
61
  input {
66
62
  position: absolute;
@@ -89,26 +85,35 @@
89
85
  --internal-size: 1.75rem;
90
86
  }
91
87
  div:has(input:hover:not(:disabled):not(:checked)) {
92
- --internal-strength-light: 6%;
93
- --internal-strength-dark: 20%;
88
+ --internal-strength-light: 8%;
89
+ --internal-strength-dark: 12%;
94
90
  }
95
91
  div:has(input:checked) {
96
92
  --internal-tint: var(--color-primary, blue);
97
93
  --internal-strength-light: 88%;
98
94
  --internal-strength-dark: 88%;
99
- --internal-border: color-mix(in srgb, var(--internal-tint) 68%, var(--color-foreground, black));
100
- --internal-highlight: color-mix(in srgb, var(--color-foreground, black) 28%, transparent);
101
- color: color-mix(in srgb, var(--color-background, white) 90%, var(--internal-tint));
95
+ --internal-border: var(--internal-fill);
96
+ color: var(--color-background, white);
102
97
  }
103
98
  div:has(input:checked:hover:not(:disabled)) {
104
99
  --internal-strength-light: 92%;
105
100
  --internal-strength-dark: 92%;
106
101
  }
102
+ div:has(input:active:not(:disabled)) {
103
+ --internal-strength-light: 12%;
104
+ --internal-strength-dark: 16%;
105
+ }
106
+ div:has(input:checked:active:not(:disabled)) {
107
+ --internal-strength-light: 96%;
108
+ --internal-strength-dark: 96%;
109
+ }
110
+ div:has(input:user-invalid),
111
+ div:has(input[aria-invalid='true']) {
112
+ --internal-border: var(--color-error, #bc2436);
113
+ }
107
114
  div:has(input:focus-visible) {
108
- --internal-border: color-mix(in srgb, var(--color-primary, blue) 55%, transparent);
109
- box-shadow:
110
- inset 0 1px 0 var(--internal-highlight),
111
- 0 0 0 3px color-mix(in srgb, var(--color-primary, blue) 16%, transparent);
115
+ outline: 2px solid var(--color-primary, blue);
116
+ outline-offset: 3px;
112
117
  }
113
118
  div:has(input:disabled) {
114
119
  opacity: 0.5;
@@ -91,9 +91,8 @@
91
91
  >
92
92
  <Popover
93
93
  bind:open={() => open, setOpen}
94
- variant="filled"
95
- spacing="small"
96
- radius="small"
94
+ spacing="x-small"
95
+ radius="0.75rem"
97
96
  shadow
98
97
  scroll
99
98
  disabled={disabled || !!attributes.readonly}
@@ -88,7 +88,7 @@
88
88
  ? undefined
89
89
  : selected.length === 1 && selected[0].icon
90
90
  ? { ...selected[0].icon }
91
- : undefined}
91
+ : { name: 'unfold_more' }}
92
92
  value={display}
93
93
  role="combobox"
94
94
  aria-autocomplete="list"
@@ -54,17 +54,17 @@
54
54
 
55
55
  <style>
56
56
  div {
57
- --internal-height: 2.5rem;
57
+ --internal-row-height: 2.125rem;
58
58
  --internal-icon-size: 1.25rem;
59
59
  display: grid;
60
- gap: 0.375rem;
60
+ gap: 0.125rem;
61
61
  }
62
62
  div[data-size='small'] {
63
- --internal-height: 2rem;
63
+ --internal-row-height: 1.875rem;
64
64
  --internal-icon-size: 1rem;
65
65
  }
66
66
  div[data-size='large'] {
67
- --internal-height: 3rem;
67
+ --internal-row-height: 2.375rem;
68
68
  --internal-icon-size: 1.5rem;
69
69
  }
70
70
  p {
@@ -72,7 +72,7 @@
72
72
  align-items: center;
73
73
  gap: 0.6rem;
74
74
  margin: 0;
75
- padding-block: 0.5rem;
75
+ padding: 0.5rem;
76
76
  color: var(--color-muted);
77
77
  }
78
78
  </style>