@realmorg/ui 0.0.0-stage → 2.0.0-alpha.3

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 (66) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +44 -2
  3. package/components/accordion.html +139 -0
  4. package/components/alert-dialog.html +236 -0
  5. package/components/alert.html +83 -0
  6. package/components/aspect-ratio.html +29 -0
  7. package/components/attachment.html +98 -0
  8. package/components/avatar.html +149 -0
  9. package/components/badge.html +62 -0
  10. package/components/breadcrumb.html +159 -0
  11. package/components/bubble.html +77 -0
  12. package/components/button-group.html +98 -0
  13. package/components/button.html +194 -0
  14. package/components/calendar.html +176 -0
  15. package/components/card.html +143 -0
  16. package/components/carousel.html +208 -0
  17. package/components/chart.html +168 -0
  18. package/components/checkbox.html +99 -0
  19. package/components/code-block.html +64 -0
  20. package/components/collapsible.html +90 -0
  21. package/components/combobox.html +237 -0
  22. package/components/command.html +201 -0
  23. package/components/context-menu.html +81 -0
  24. package/components/dialog.html +327 -0
  25. package/components/direction.html +23 -0
  26. package/components/drawer.html +267 -0
  27. package/components/dropdown-menu.html +421 -0
  28. package/components/empty.html +132 -0
  29. package/components/field.html +143 -0
  30. package/components/hover-card.html +111 -0
  31. package/components/input-group.html +91 -0
  32. package/components/input-otp.html +151 -0
  33. package/components/input.html +94 -0
  34. package/components/item.html +208 -0
  35. package/components/kbd.html +45 -0
  36. package/components/label.html +46 -0
  37. package/components/marker.html +41 -0
  38. package/components/menubar.html +137 -0
  39. package/components/message-scroller.html +68 -0
  40. package/components/message.html +95 -0
  41. package/components/navigation-menu.html +233 -0
  42. package/components/pagination.html +160 -0
  43. package/components/popover.html +164 -0
  44. package/components/progress.html +45 -0
  45. package/components/radio-group.html +120 -0
  46. package/components/resizable.html +170 -0
  47. package/components/scroll-area.html +56 -0
  48. package/components/select.html +103 -0
  49. package/components/separator.html +31 -0
  50. package/components/sheet.html +173 -0
  51. package/components/sidebar.html +393 -0
  52. package/components/skeleton.html +36 -0
  53. package/components/slider.html +108 -0
  54. package/components/spinner.html +53 -0
  55. package/components/switch.html +95 -0
  56. package/components/table.html +81 -0
  57. package/components/tabs.html +182 -0
  58. package/components/textarea.html +80 -0
  59. package/components/toaster.html +134 -0
  60. package/components/toggle-group.html +71 -0
  61. package/components/toggle.html +87 -0
  62. package/components/tooltip.html +114 -0
  63. package/dist/theme.css +158 -0
  64. package/dist/ui.html +7923 -0
  65. package/package.json +40 -3
  66. package/theme.css +158 -0
@@ -0,0 +1,81 @@
1
+ <custom-element name="ui-table">
2
+ <script>
3
+ const props = defineProps({
4
+ columns: prop.array(prop.object({ key: prop.string().required(), label: prop.string() })).default([]),
5
+ rows: prop.array(prop.object()).default([]),
6
+ caption: prop.string(),
7
+ });
8
+
9
+ const table = useState({
10
+ get headers() {
11
+ return props.columns.map((column) => ({ key: column.key, label: column.label ?? column.key }));
12
+ },
13
+ get grid() {
14
+ return props.rows.map((row, index) => ({
15
+ id: row.id ?? index,
16
+ cells: props.columns.map((column) => ({ key: column.key, value: row[column.key] ?? "" })),
17
+ }));
18
+ },
19
+ });
20
+ </script>
21
+
22
+ <style>
23
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
24
+ touch-action does not reach shadow content in iOS Safari. */
25
+ :host,
26
+ * {
27
+ touch-action: manipulation;
28
+ }
29
+ :host {
30
+ display: block;
31
+ width: 100%;
32
+ box-sizing: border-box;
33
+ overflow-x: auto;
34
+ }
35
+ table {
36
+ width: 100%;
37
+ border-collapse: collapse;
38
+ font-size: var(--text-sm);
39
+ caption-side: bottom;
40
+ }
41
+ caption {
42
+ margin-top: calc(var(--spacing) * 4);
43
+ color: var(--muted-foreground);
44
+ }
45
+ th {
46
+ height: calc(var(--spacing) * 10);
47
+ padding: 0 calc(var(--spacing) * 2);
48
+ text-align: left;
49
+ font-weight: var(--font-weight-medium);
50
+ white-space: nowrap;
51
+ color: var(--foreground);
52
+ }
53
+ td {
54
+ padding: calc(var(--spacing) * 2);
55
+ white-space: nowrap;
56
+ }
57
+ tr {
58
+ border-bottom: 1px solid var(--border);
59
+ }
60
+ tbody tr:last-child {
61
+ border-bottom: 0;
62
+ }
63
+ tbody tr:hover {
64
+ background: color-mix(in oklab, var(--muted) 50%, transparent);
65
+ }
66
+ </style>
67
+
68
+ <table part="table">
69
+ <caption _hidden="!@caption"><slot name="@caption"></slot></caption>
70
+ <thead>
71
+ <tr>
72
+ <th repeat="#table.headers" as="column" key="key" part="head"><slot name="#column.label"></slot></th>
73
+ </tr>
74
+ </thead>
75
+ <tbody>
76
+ <tr repeat="#table.grid" as="row" key="id" part="row">
77
+ <td repeat="#row.cells" as="cell" key="key" part="cell"><slot name="#cell.value"></slot></td>
78
+ </tr>
79
+ </tbody>
80
+ </table>
81
+ </custom-element>
@@ -0,0 +1,182 @@
1
+ <custom-element name="ui-tabs">
2
+ <script>
3
+ const props = defineProps({
4
+ value: prop.string(),
5
+ orientation: prop.enum(["horizontal", "vertical"]).default("horizontal"),
6
+ });
7
+
8
+ const parts = useChildren("ui-tabs-list, ui-tabs-content", ["value", "disabled"]);
9
+ const tabs = useState({
10
+ get orientation() {
11
+ return props.orientation;
12
+ },
13
+ get focusgroup() {
14
+ return `tablist ${props.orientation === "vertical" ? "block" : "inline"} wrap`;
15
+ },
16
+ get triggers() {
17
+ return parts.flatMap((part) => [...part.querySelectorAll("ui-tabs-trigger")]);
18
+ },
19
+ get current() {
20
+ const enabled = this.triggers.filter((trigger) => !trigger.hasAttribute("disabled")).map((trigger) => trigger.getAttribute("value"));
21
+ return enabled.includes(props.value) ? props.value : (enabled[0] ?? null);
22
+ },
23
+ });
24
+
25
+ function select(value) {
26
+ if (value === tabs.current) return;
27
+ props.value = value;
28
+ dispatch("change");
29
+ }
30
+
31
+ observe(() => {
32
+ for (const content of parts.filter((part) => part.localName === "ui-tabs-content")) {
33
+ const value = content.getAttribute("value");
34
+ content.hidden = value !== tabs.current;
35
+ content.ariaLabelledByElements = tabs.triggers.filter((trigger) => trigger.getAttribute("value") === value);
36
+ }
37
+ });
38
+ </script>
39
+
40
+ <style>
41
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
42
+ touch-action does not reach shadow content in iOS Safari. */
43
+ :host,
44
+ * {
45
+ touch-action: manipulation;
46
+ }
47
+ :host {
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: calc(var(--spacing) * 2);
51
+ }
52
+ :host(:state(attr-orientation-is-vertical)) {
53
+ flex-direction: row;
54
+ --ui-tabs-list-direction: column;
55
+ --ui-tabs-list-height: fit-content;
56
+ }
57
+ </style>
58
+
59
+ <slot></slot>
60
+ </custom-element>
61
+
62
+ <custom-element name="ui-tabs-list">
63
+ <style>
64
+ :host,
65
+ * {
66
+ touch-action: manipulation;
67
+ }
68
+ :host {
69
+ display: block;
70
+ width: fit-content;
71
+ }
72
+ [role="tablist"] {
73
+ display: inline-flex;
74
+ flex-direction: var(--ui-tabs-list-direction, row);
75
+ height: var(--ui-tabs-list-height, calc(var(--spacing) * 9));
76
+ align-items: center;
77
+ justify-content: center;
78
+ padding: 3px;
79
+ box-sizing: border-box;
80
+ border-radius: var(--radius-lg);
81
+ background: var(--muted);
82
+ color: var(--muted-foreground);
83
+ }
84
+ </style>
85
+
86
+ <div role="tablist" part="list" _aria-orientation="#tabs.orientation" _focusgroup="#tabs.focusgroup"><slot></slot></div>
87
+ </custom-element>
88
+
89
+ <custom-element name="ui-tabs-trigger">
90
+ <script>
91
+ const props = defineProps({ value: prop.string(), disabled: prop.boolean() });
92
+ const tabs = lookup("tabs");
93
+ const tab = useState({
94
+ get selected() {
95
+ return props.value === tabs.current;
96
+ },
97
+ get tabindex() {
98
+ return this.selected ? 0 : -1;
99
+ },
100
+ });
101
+ </script>
102
+
103
+ <style>
104
+ :host,
105
+ * {
106
+ touch-action: manipulation;
107
+ }
108
+ :host {
109
+ display: flex;
110
+ flex: 1;
111
+ align-self: stretch;
112
+ }
113
+ button {
114
+ all: unset;
115
+ box-sizing: border-box;
116
+ display: inline-flex;
117
+ flex: 1;
118
+ height: calc(100% - 1px);
119
+ align-items: center;
120
+ justify-content: center;
121
+ gap: calc(var(--spacing) * 1.5);
122
+ padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2);
123
+ border: 1px solid transparent;
124
+ border-radius: var(--radius-md);
125
+ font-size: var(--text-sm);
126
+ font-weight: var(--font-weight-medium);
127
+ white-space: nowrap;
128
+ color: color-mix(in oklab, var(--foreground) 60%, transparent);
129
+ cursor: pointer;
130
+ transition: color var(--default-transition-duration), box-shadow var(--default-transition-duration);
131
+ }
132
+ button:hover {
133
+ color: var(--foreground);
134
+ }
135
+ button[aria-selected="true"] {
136
+ background: var(--background);
137
+ color: var(--foreground);
138
+ box-shadow: var(--shadow-xs);
139
+ }
140
+ button:focus-visible {
141
+ border-color: var(--ring);
142
+ box-shadow: var(--focus-ring);
143
+ }
144
+ button:disabled {
145
+ pointer-events: none;
146
+ opacity: 0.5;
147
+ }
148
+ ::slotted(svg) {
149
+ width: calc(var(--spacing) * 4);
150
+ height: calc(var(--spacing) * 4);
151
+ flex-shrink: 0;
152
+ }
153
+ </style>
154
+
155
+ <button type="button"
156
+ role="tab"
157
+ part="trigger"
158
+ _aria-selected="#tab.selected"
159
+ _tabindex="#tab.tabindex"
160
+ _disabled="@disabled"
161
+ onclick="select(props.value)"
162
+ onfocus="select(props.value)"><slot></slot></button>
163
+ </custom-element>
164
+
165
+ <custom-element name="ui-tabs-content" role="tabpanel">
166
+ <style>
167
+ :host,
168
+ * {
169
+ touch-action: manipulation;
170
+ }
171
+ :host {
172
+ display: block;
173
+ flex: 1;
174
+ outline: none;
175
+ }
176
+ :host([hidden]) {
177
+ display: none;
178
+ }
179
+ </style>
180
+
181
+ <slot></slot>
182
+ </custom-element>
@@ -0,0 +1,80 @@
1
+ <custom-element name="ui-textarea" form-associated delegates-focus>
2
+ <script>
3
+ defineProps({
4
+ value: prop.string().default(""),
5
+ placeholder: prop.string(),
6
+ rows: prop.number().int().min(1).default(3),
7
+ minlength: prop.number(),
8
+ maxlength: prop.number(),
9
+ disabled: prop.boolean(),
10
+ readonly: prop.boolean(),
11
+ required: prop.boolean(),
12
+ invalid: prop.boolean(),
13
+ label: prop.string().attribute("aria-label"),
14
+ });
15
+ </script>
16
+
17
+ <style>
18
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
19
+ touch-action does not reach shadow content in iOS Safari. */
20
+ :host,
21
+ * {
22
+ touch-action: manipulation;
23
+ }
24
+ :host {
25
+ display: block;
26
+ }
27
+ textarea {
28
+ box-sizing: border-box;
29
+ display: block;
30
+ width: 100%;
31
+ min-height: calc(var(--spacing) * 16);
32
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
33
+ border: 1px solid var(--input);
34
+ border-radius: var(--radius-md);
35
+ background: transparent;
36
+ color: inherit;
37
+ font: inherit;
38
+ font-size: var(--text-sm);
39
+ box-shadow: var(--shadow-xs);
40
+ outline: none;
41
+ field-sizing: content;
42
+ resize: vertical;
43
+ }
44
+ /* iOS Safari zooms in on a focused field whose text is under 16px. */
45
+ @media (pointer: coarse) {
46
+ textarea {
47
+ font-size: var(--text-base);
48
+ }
49
+ }
50
+ textarea::placeholder {
51
+ color: var(--muted-foreground);
52
+ }
53
+ textarea:focus-visible {
54
+ border-color: var(--ring);
55
+ box-shadow: var(--focus-ring);
56
+ }
57
+ textarea:disabled {
58
+ cursor: not-allowed;
59
+ opacity: 0.5;
60
+ }
61
+ textarea[aria-invalid="true"] {
62
+ border-color: var(--destructive);
63
+ }
64
+ </style>
65
+
66
+ <textarea
67
+ ref="control"
68
+ _aria-label="@label"
69
+ part="textarea"
70
+ _value="@value"
71
+ _placeholder="@placeholder"
72
+ _rows="@rows"
73
+ _minlength="@minlength"
74
+ _maxlength="@maxlength"
75
+ _disabled="@disabled"
76
+ _readonly="@readonly"
77
+ _required="@required"
78
+ _aria-invalid="@invalid"
79
+ ></textarea>
80
+ </custom-element>
@@ -0,0 +1,134 @@
1
+ <custom-element name="ui-toaster">
2
+ <script>
3
+ const props = defineProps({
4
+ position: prop.enum(["bottom-right", "bottom-left", "bottom-center", "top-right", "top-left", "top-center"]).default("bottom-right"),
5
+ duration: prop.number().min(0).default(4000),
6
+ });
7
+
8
+ const toasts = useState({ list: [] });
9
+ const timers = new Map();
10
+ let counter = 0;
11
+
12
+ function dismiss(toast) {
13
+ clearTimeout(timers.get(toast.id));
14
+ timers.delete(toast.id);
15
+ const index = toasts.list.findIndex((item) => item.id === toast.id);
16
+ if (index !== -1) toasts.list.splice(index, 1);
17
+ }
18
+
19
+ function add(detail = {}) {
20
+ const toast = {
21
+ id: ++counter,
22
+ title: detail.title ?? "",
23
+ description: detail.description ?? "",
24
+ variant: detail.variant ?? "default",
25
+ };
26
+ toasts.list.push(toast);
27
+ const duration = detail.duration ?? props.duration;
28
+ if (duration > 0) timers.set(toast.id, setTimeout(() => dismiss(toast), duration));
29
+ return toast.id;
30
+ }
31
+
32
+ defineMethods({ toast: add, dismiss: (id) => dismiss({ id }) });
33
+ listen("toast", (event) => add(event.detail), document);
34
+ onConnected(() => {
35
+ observe(() => {
36
+ const region = refs.region;
37
+ const open = toasts.list.length > 0;
38
+ if (!region.isConnected) return;
39
+ if (open && !region.matches(":popover-open")) region.showPopover();
40
+ if (!open && region.matches(":popover-open")) region.hidePopover();
41
+ });
42
+ });
43
+ </script>
44
+
45
+ <style>
46
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
47
+ touch-action does not reach shadow content in iOS Safari. */
48
+ :host,
49
+ * {
50
+ touch-action: manipulation;
51
+ }
52
+ ol {
53
+ position: fixed;
54
+ inset: auto calc(var(--spacing) * 4) calc(var(--spacing) * 4) auto;
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: calc(var(--spacing) * 2);
58
+ width: min(calc(var(--spacing) * 88), calc(100% - calc(var(--spacing) * 8)));
59
+ margin: 0;
60
+ padding: 0;
61
+ border: 0;
62
+ background: transparent;
63
+ list-style: none;
64
+ overflow: visible;
65
+ }
66
+ :host(:state(attr-position-is-bottom-left)) ol {
67
+ inset: auto auto calc(var(--spacing) * 4) calc(var(--spacing) * 4);
68
+ }
69
+ :host(:state(attr-position-is-bottom-center)) ol {
70
+ inset: auto 0 calc(var(--spacing) * 4) 0;
71
+ margin: 0 auto;
72
+ }
73
+ :host(:state(attr-position-is-top-right)) ol {
74
+ inset: calc(var(--spacing) * 4) calc(var(--spacing) * 4) auto auto;
75
+ }
76
+ :host(:state(attr-position-is-top-left)) ol {
77
+ inset: calc(var(--spacing) * 4) auto auto calc(var(--spacing) * 4);
78
+ }
79
+ :host(:state(attr-position-is-top-center)) ol {
80
+ inset: calc(var(--spacing) * 4) 0 auto 0;
81
+ margin: 0 auto;
82
+ }
83
+ li {
84
+ position: relative;
85
+ display: grid;
86
+ gap: calc(var(--spacing) * 1);
87
+ padding: calc(var(--spacing) * 4) calc(var(--spacing) * 10) calc(var(--spacing) * 4) calc(var(--spacing) * 4);
88
+ border: 1px solid var(--border);
89
+ border-radius: var(--radius-lg);
90
+ background: var(--popover);
91
+ color: var(--popover-foreground);
92
+ font-size: var(--text-sm);
93
+ box-shadow: var(--shadow-md);
94
+ animation: enter 0.2s ease-out;
95
+ }
96
+ li[data-variant="destructive"] {
97
+ border-color: var(--destructive);
98
+ color: var(--destructive);
99
+ }
100
+ strong {
101
+ font-weight: var(--font-weight-semibold);
102
+ }
103
+ p {
104
+ margin: 0;
105
+ color: var(--muted-foreground);
106
+ }
107
+ button {
108
+ all: unset;
109
+ position: absolute;
110
+ top: calc(var(--spacing) * 3);
111
+ right: calc(var(--spacing) * 3);
112
+ cursor: pointer;
113
+ opacity: 0.6;
114
+ }
115
+ button:hover,
116
+ button:focus-visible {
117
+ opacity: 1;
118
+ }
119
+ @keyframes enter {
120
+ from {
121
+ opacity: 0;
122
+ translate: 0 calc(var(--spacing) * 2);
123
+ }
124
+ }
125
+ </style>
126
+
127
+ <ol ref="region" part="region" popover="manual" aria-live="polite">
128
+ <li repeat="#toasts.list" as="toast" key="id" part="toast" _data-variant="#toast.variant">
129
+ <strong><slot name="#toast.title"></slot></strong>
130
+ <p _hidden="!#toast.description"><slot name="#toast.description"></slot></p>
131
+ <button type="button" aria-label="Dismiss" onclick="dismiss(toast)">×</button>
132
+ </li>
133
+ </ol>
134
+ </custom-element>
@@ -0,0 +1,71 @@
1
+ <import-element from="./toggle.html"></import-element>
2
+
3
+ <custom-element name="ui-toggle-group" role="group" form-associated>
4
+ <script>
5
+ const props = defineProps({
6
+ type: prop.enum(["single", "multiple"]).default("single"),
7
+ value: prop.string().default(""),
8
+ orientation: prop.enum(["horizontal", "vertical"]).default("horizontal"),
9
+ });
10
+
11
+ const toggles = useChildren("ui-toggle");
12
+ const group = useState({
13
+ get focusgroup() {
14
+ return `toolbar ${props.orientation === "vertical" ? "block" : "inline"} wrap`;
15
+ },
16
+ get selected() {
17
+ return new Set(props.value.split(",").filter(Boolean));
18
+ },
19
+ });
20
+
21
+ function changed(event) {
22
+ const toggle = event.target;
23
+ if (toggle.localName !== "ui-toggle") return;
24
+ event.stopPropagation();
25
+ const value = toggle.value;
26
+ const selected = props.type === "single" ? new Set(toggle.pressed ? [value] : []) : new Set(group.selected);
27
+ if (props.type === "multiple") selected[toggle.pressed ? "add" : "delete"](value);
28
+ props.value = [...selected].join(",");
29
+ dispatch("change");
30
+ }
31
+
32
+ listen("change", changed);
33
+
34
+ observe(() => {
35
+ for (const toggle of toggles) toggle.pressed = group.selected.has(toggle.value);
36
+ });
37
+ </script>
38
+
39
+ <style>
40
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
41
+ touch-action does not reach shadow content in iOS Safari. */
42
+ :host,
43
+ * {
44
+ touch-action: manipulation;
45
+ }
46
+ :host {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ width: fit-content;
50
+ border-radius: var(--radius-md);
51
+ }
52
+ :host(:state(attr-orientation-is-vertical)) {
53
+ flex-direction: column;
54
+ align-items: stretch;
55
+ }
56
+ :host(:not(:state(attr-orientation-is-vertical))) ::slotted(:not(:first-child)) {
57
+ --ui-radius-start: 0;
58
+ }
59
+ :host(:not(:state(attr-orientation-is-vertical))) ::slotted(:not(:last-child)) {
60
+ --ui-radius-end: 0;
61
+ }
62
+ :host(:state(attr-orientation-is-vertical)) ::slotted(:not(:first-child)) {
63
+ --ui-radius-block-start: 0;
64
+ }
65
+ :host(:state(attr-orientation-is-vertical)) ::slotted(:not(:last-child)) {
66
+ --ui-radius-block-end: 0;
67
+ }
68
+ </style>
69
+
70
+ <div part="group" _focusgroup="#group.focusgroup" style="display: contents"><slot></slot></div>
71
+ </custom-element>
@@ -0,0 +1,87 @@
1
+ <custom-element name="ui-toggle" delegates-focus>
2
+ <script>
3
+ const props = defineProps({
4
+ pressed: prop.boolean().reflect(),
5
+ variant: prop.enum(["default", "outline"]).default("default"),
6
+ size: prop.enum(["default", "sm", "lg"]).default("default"),
7
+ disabled: prop.boolean(),
8
+ value: prop.string(),
9
+ label: prop.string().attribute("aria-label"),
10
+ });
11
+
12
+ function toggle() {
13
+ props.pressed = !props.pressed;
14
+ dispatch("change");
15
+ }
16
+ </script>
17
+
18
+ <style>
19
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
20
+ touch-action does not reach shadow content in iOS Safari. */
21
+ :host,
22
+ * {
23
+ touch-action: manipulation;
24
+ }
25
+ :host {
26
+ display: inline-flex;
27
+ }
28
+ button {
29
+ all: unset;
30
+ box-sizing: border-box;
31
+ display: inline-flex;
32
+ flex: 1;
33
+ align-items: center;
34
+ justify-content: center;
35
+ gap: calc(var(--spacing) * 2);
36
+ min-width: calc(var(--spacing) * 9);
37
+ height: calc(var(--spacing) * 9);
38
+ padding: 0 calc(var(--spacing) * 2);
39
+ border: 1px solid transparent;
40
+ border-radius: var(--radius-md);
41
+ border-start-start-radius: var(--ui-radius-block-start, var(--ui-radius-start, var(--radius-md)));
42
+ border-end-start-radius: var(--ui-radius-block-end, var(--ui-radius-start, var(--radius-md)));
43
+ border-start-end-radius: var(--ui-radius-block-start, var(--ui-radius-end, var(--radius-md)));
44
+ border-end-end-radius: var(--ui-radius-block-end, var(--ui-radius-end, var(--radius-md)));
45
+ font-size: var(--text-sm);
46
+ font-weight: var(--font-weight-medium);
47
+ cursor: pointer;
48
+ transition: background-color var(--default-transition-duration), color var(--default-transition-duration);
49
+ }
50
+ button:hover {
51
+ background: var(--muted);
52
+ color: var(--muted-foreground);
53
+ }
54
+ :host(:state(attr-pressed)) button {
55
+ background: var(--accent);
56
+ color: var(--accent-foreground);
57
+ }
58
+ :host(:state(attr-variant-is-outline)) button {
59
+ border-color: var(--input);
60
+ box-shadow: var(--shadow-xs);
61
+ }
62
+ :host(:state(attr-size-is-sm)) button {
63
+ min-width: calc(var(--spacing) * 8);
64
+ height: calc(var(--spacing) * 8);
65
+ padding: 0 calc(var(--spacing) * 1.5);
66
+ }
67
+ :host(:state(attr-size-is-lg)) button {
68
+ min-width: calc(var(--spacing) * 10);
69
+ height: calc(var(--spacing) * 10);
70
+ padding: 0 calc(var(--spacing) * 2.5);
71
+ }
72
+ button:focus-visible {
73
+ border-color: var(--ring);
74
+ box-shadow: var(--focus-ring);
75
+ }
76
+ button:disabled {
77
+ pointer-events: none;
78
+ opacity: 0.5;
79
+ }
80
+ ::slotted(svg) {
81
+ width: calc(var(--spacing) * 4);
82
+ height: calc(var(--spacing) * 4);
83
+ }
84
+ </style>
85
+
86
+ <button type="button" part="button" _aria-pressed="@pressed" _aria-label="@label" _disabled="@disabled" onclick="toggle()"><slot></slot></button>
87
+ </custom-element>