@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,111 @@
1
+ <custom-element name="ui-hover-card">
2
+ <script>
3
+ const props = defineProps({
4
+ openDelay: prop.number().attribute("open-delay").min(0).default(500),
5
+ closeDelay: prop.number().attribute("close-delay").min(0).default(300),
6
+ });
7
+
8
+ let timer;
9
+ let source;
10
+
11
+ function apply(open) {
12
+ const card = host.querySelector(":scope > ui-hover-card-content");
13
+ if (card.matches(":popover-open") === open) return;
14
+ if (open) card.showPopover({ source });
15
+ else card.hidePopover();
16
+ }
17
+
18
+ function schedule(open, trigger = source) {
19
+ source = trigger;
20
+ clearTimeout(timer);
21
+ timer = setTimeout(() => apply(open), open ? props.openDelay : props.closeDelay);
22
+ }
23
+
24
+ function dismiss(event) {
25
+ if (event.key !== "Escape") return;
26
+ clearTimeout(timer);
27
+ apply(false);
28
+ }
29
+
30
+ listen("keydown", dismiss, document);
31
+ </script>
32
+
33
+ <style>
34
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
35
+ touch-action does not reach shadow content in iOS Safari. */
36
+ :host,
37
+ * {
38
+ touch-action: manipulation;
39
+ }
40
+ :host {
41
+ display: contents;
42
+ }
43
+ </style>
44
+
45
+ <slot></slot>
46
+ </custom-element>
47
+
48
+ <custom-element name="ui-hover-card-trigger">
49
+ <style>
50
+ :host,
51
+ * {
52
+ touch-action: manipulation;
53
+ }
54
+ :host {
55
+ display: inline-block;
56
+ }
57
+ </style>
58
+
59
+ <span part="trigger" onpointerenter="schedule(true, host)" onpointerleave="schedule(false)" onfocusin="schedule(true, host)" onfocusout="schedule(false)"><slot></slot></span>
60
+ </custom-element>
61
+
62
+ <custom-element name="ui-hover-card-content">
63
+ <script>
64
+ defineProps({
65
+ placement: prop.enum(["bottom", "top", "left", "right"]).default("bottom"),
66
+ });
67
+
68
+ attrs.popover ??= "manual";
69
+ </script>
70
+
71
+ <style>
72
+ :host,
73
+ * {
74
+ touch-action: manipulation;
75
+ }
76
+ :host {
77
+ box-sizing: border-box;
78
+ width: calc(var(--spacing) * 64);
79
+ margin: calc(var(--spacing) * 1);
80
+ padding: 0;
81
+ border: 1px solid var(--border);
82
+ border-radius: var(--radius-md);
83
+ background: var(--popover);
84
+ color: var(--popover-foreground);
85
+ box-shadow: var(--shadow-md);
86
+ inset: auto;
87
+ height: fit-content;
88
+ max-height: calc(100svh - calc(var(--spacing) * 8));
89
+ overflow-y: auto;
90
+ position-area: bottom;
91
+ position-try-fallbacks: flip-block, flip-inline;
92
+ }
93
+ :host(:state(attr-width)) {
94
+ width: calc(var(--spacing) * var(--attr-width));
95
+ }
96
+ :host(:state(attr-placement-is-top)) {
97
+ position-area: top;
98
+ }
99
+ :host(:state(attr-placement-is-left)) {
100
+ position-area: left;
101
+ }
102
+ :host(:state(attr-placement-is-right)) {
103
+ position-area: right;
104
+ }
105
+ div {
106
+ padding: calc(var(--spacing) * 4);
107
+ }
108
+ </style>
109
+
110
+ <div part="content" onpointerenter="schedule(true)" onpointerleave="schedule(false)"><slot></slot></div>
111
+ </custom-element>
@@ -0,0 +1,91 @@
1
+ <custom-element name="ui-input-group" form-associated delegates-focus>
2
+ <script>
3
+ defineProps({
4
+ value: prop.string().default(""),
5
+ type: prop.enum(["text", "email", "password", "number", "search", "tel", "url"]).default("text"),
6
+ placeholder: prop.string(),
7
+ disabled: prop.boolean(),
8
+ required: prop.boolean(),
9
+ label: prop.string().attribute("aria-label"),
10
+ });
11
+ </script>
12
+
13
+ <style>
14
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
15
+ touch-action does not reach shadow content in iOS Safari. */
16
+ :host,
17
+ * {
18
+ touch-action: manipulation;
19
+ }
20
+ :host {
21
+ display: flex;
22
+ align-items: center;
23
+ min-width: 0;
24
+ height: calc(var(--spacing) * 9);
25
+ box-sizing: border-box;
26
+ border: 1px solid var(--input);
27
+ border-radius: var(--radius-md);
28
+ box-shadow: var(--shadow-xs);
29
+ transition: box-shadow var(--default-transition-duration);
30
+ }
31
+ :host(:focus-within) {
32
+ border-color: var(--ring);
33
+ box-shadow: var(--focus-ring);
34
+ }
35
+ input {
36
+ flex: 1;
37
+ min-width: 0;
38
+ height: 100%;
39
+ padding: 0 calc(var(--spacing) * 3);
40
+ border: 0;
41
+ background: transparent;
42
+ color: inherit;
43
+ font: inherit;
44
+ font-size: var(--text-sm);
45
+ outline: none;
46
+ }
47
+ /* iOS Safari zooms in on a focused field whose text is under 16px. */
48
+ @media (pointer: coarse) {
49
+ input {
50
+ font-size: var(--text-base);
51
+ }
52
+ }
53
+ input::placeholder {
54
+ color: var(--muted-foreground);
55
+ }
56
+ </style>
57
+
58
+ <slot></slot>
59
+ <input ref="control" part="input" _type="@type" _value="@value" _placeholder="@placeholder" _disabled="@disabled" _required="@required" _aria-label="@label">
60
+ </custom-element>
61
+
62
+ <custom-element name="ui-input-group-addon">
63
+ <style>
64
+ :host,
65
+ * {
66
+ touch-action: manipulation;
67
+ }
68
+ :host {
69
+ display: flex;
70
+ order: -1;
71
+ align-items: center;
72
+ gap: calc(var(--spacing) * 2);
73
+ height: 100%;
74
+ padding-inline-start: calc(var(--spacing) * 3);
75
+ font-size: var(--text-sm);
76
+ color: var(--muted-foreground);
77
+ cursor: text;
78
+ user-select: none;
79
+ }
80
+ :host(:state(attr-align-is-inline-end)) {
81
+ order: 1;
82
+ padding-inline: 0 calc(var(--spacing) * 3);
83
+ }
84
+ ::slotted(svg) {
85
+ width: calc(var(--spacing) * 4);
86
+ height: calc(var(--spacing) * 4);
87
+ }
88
+ </style>
89
+
90
+ <slot></slot>
91
+ </custom-element>
@@ -0,0 +1,151 @@
1
+ <custom-element name="ui-input-otp" form-associated>
2
+ <script>
3
+ const props = defineProps({
4
+ value: prop.string().default(""),
5
+ length: prop.number().int().min(1).max(12).default(6),
6
+ pattern: prop.string().default("[0-9]"),
7
+ separator: prop.number().int().min(0).default(0),
8
+ disabled: prop.boolean(),
9
+ required: prop.boolean(),
10
+ });
11
+
12
+ const otp = useState({
13
+ get slots() {
14
+ return Array.from({ length: props.length }, (_, index) => ({
15
+ index,
16
+ char: props.value[index] ?? "",
17
+ gap: props.separator > 0 && index > 0 && index % props.separator === 0,
18
+ label: `Character ${index + 1} of ${props.length}`,
19
+ }));
20
+ },
21
+ get complete() {
22
+ return props.value.length === props.length;
23
+ },
24
+ });
25
+
26
+ const KEYS = { ArrowLeft: -1, ArrowRight: 1 };
27
+
28
+ function boxes() {
29
+ return [...refs.group.querySelectorAll("input")];
30
+ }
31
+
32
+ function accept(text) {
33
+ const matcher = new RegExp(`^${props.pattern}$`);
34
+ return [...text].filter((char) => matcher.test(char)).join("");
35
+ }
36
+
37
+ async function write(start, text) {
38
+ const chars = [...props.value.padEnd(start, " ")];
39
+ [...accept(text)].forEach((char, offset) => {
40
+ chars[start + offset] = char;
41
+ });
42
+ props.value = chars.join("").slice(0, props.length).trimEnd();
43
+ dispatch("change");
44
+ if (otp.complete) dispatch("complete", { value: props.value });
45
+ await tick();
46
+ boxes()[Math.min(start + accept(text).length, props.length - 1)]?.focus();
47
+ }
48
+
49
+ function input(slot, field) {
50
+ const text = field.value;
51
+ field.value = slot.char;
52
+ if (text) write(slot.index, text.slice(-1));
53
+ }
54
+
55
+ function paste(slot, event) {
56
+ event.preventDefault();
57
+ write(slot.index, event.clipboardData.getData("text"));
58
+ }
59
+
60
+ function keys(slot, event) {
61
+ const step = KEYS[event.key];
62
+ if (step) boxes()[slot.index + step]?.focus();
63
+ if (event.key !== "Backspace") return;
64
+ event.preventDefault();
65
+ const target = slot.char ? slot.index : slot.index - 1;
66
+ if (target < 0) return;
67
+ props.value = props.value.slice(0, target) + props.value.slice(target + 1);
68
+ boxes()[target]?.focus();
69
+ dispatch("change");
70
+ }
71
+
72
+ observe(() => {
73
+ const missing = props.required && !otp.complete;
74
+ const anchor = refs.group?.querySelectorAll("input")[Math.min(props.value.length, props.length - 1)];
75
+ internals.setValidity(missing ? { valueMissing: true } : {}, missing ? "Enter the full code." : "", anchor);
76
+ });
77
+ </script>
78
+
79
+ <style>
80
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
81
+ touch-action does not reach shadow content in iOS Safari. */
82
+ :host,
83
+ * {
84
+ touch-action: manipulation;
85
+ }
86
+ :host {
87
+ display: inline-flex;
88
+ }
89
+ .group {
90
+ display: flex;
91
+ align-items: center;
92
+ }
93
+ input {
94
+ box-sizing: border-box;
95
+ width: calc(var(--spacing) * 9);
96
+ height: calc(var(--spacing) * 9);
97
+ margin-left: -1px;
98
+ border: 1px solid var(--input);
99
+ background: transparent;
100
+ color: inherit;
101
+ font: inherit;
102
+ font-size: var(--text-sm);
103
+ text-align: center;
104
+ outline: none;
105
+ }
106
+ /* iOS Safari zooms in on a focused field whose text is under 16px. */
107
+ @media (pointer: coarse) {
108
+ input {
109
+ font-size: var(--text-base);
110
+ }
111
+ }
112
+ input:first-child,
113
+ input[data-gap] {
114
+ margin-left: 0;
115
+ border-start-start-radius: var(--radius-md);
116
+ border-end-start-radius: var(--radius-md);
117
+ }
118
+ input:last-child {
119
+ border-start-end-radius: var(--radius-md);
120
+ border-end-end-radius: var(--radius-md);
121
+ }
122
+ input[data-gap] {
123
+ margin-inline-start: calc(var(--spacing) * 6);
124
+ }
125
+ input:focus-visible {
126
+ position: relative;
127
+ border-color: var(--ring);
128
+ box-shadow: var(--focus-ring);
129
+ }
130
+ input:disabled {
131
+ opacity: 0.5;
132
+ }
133
+ </style>
134
+
135
+ <div class="group" ref="group" part="group" role="group">
136
+ <repeat-list of="#otp.slots" as="slot" key="index">
137
+ <input
138
+ part="slot"
139
+ inputmode="numeric"
140
+ autocomplete="one-time-code"
141
+ maxlength="2"
142
+ _value="#slot.char"
143
+ _aria-label="#slot.label"
144
+ _data-gap="#slot.gap"
145
+ _disabled="@disabled"
146
+ oninput="input(slot, this)"
147
+ onpaste="paste(slot, event)"
148
+ onkeydown="keys(slot, event)">
149
+ </repeat-list>
150
+ </div>
151
+ </custom-element>
@@ -0,0 +1,94 @@
1
+ <custom-element name="ui-input" form-associated delegates-focus>
2
+ <script>
3
+ defineProps({
4
+ value: prop.string().default(""),
5
+ type: prop
6
+ .enum(["text", "email", "password", "number", "search", "tel", "url", "date", "time", "datetime-local", "month", "week", "color"])
7
+ .default("text"),
8
+ placeholder: prop.string(),
9
+ autocomplete: prop.string(),
10
+ inputmode: prop.string(),
11
+ pattern: prop.string(),
12
+ min: prop.string(),
13
+ max: prop.string(),
14
+ step: prop.string(),
15
+ minlength: prop.number(),
16
+ maxlength: prop.number(),
17
+ disabled: prop.boolean(),
18
+ readonly: prop.boolean(),
19
+ required: prop.boolean(),
20
+ invalid: prop.boolean(),
21
+ label: prop.string().attribute("aria-label"),
22
+ });
23
+ </script>
24
+
25
+ <style>
26
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
27
+ touch-action does not reach shadow content in iOS Safari. */
28
+ :host,
29
+ * {
30
+ touch-action: manipulation;
31
+ }
32
+ :host {
33
+ display: block;
34
+ min-width: 0;
35
+ }
36
+ input {
37
+ box-sizing: border-box;
38
+ width: 100%;
39
+ height: calc(var(--spacing) * 9);
40
+ padding: calc(var(--spacing) * 1) calc(var(--spacing) * 3);
41
+ border: 1px solid var(--input);
42
+ border-radius: var(--radius-md);
43
+ background: transparent;
44
+ color: inherit;
45
+ font: inherit;
46
+ font-size: var(--text-sm);
47
+ box-shadow: var(--shadow-xs);
48
+ outline: none;
49
+ transition: color var(--default-transition-duration), box-shadow var(--default-transition-duration);
50
+ }
51
+ /* iOS Safari zooms in on a focused field whose text is under 16px. */
52
+ @media (pointer: coarse) {
53
+ input {
54
+ font-size: var(--text-base);
55
+ }
56
+ }
57
+ input::placeholder {
58
+ color: var(--muted-foreground);
59
+ }
60
+ input:focus-visible {
61
+ border-color: var(--ring);
62
+ box-shadow: var(--focus-ring);
63
+ }
64
+ input:disabled {
65
+ cursor: not-allowed;
66
+ opacity: 0.5;
67
+ }
68
+ input[aria-invalid="true"] {
69
+ border-color: var(--destructive);
70
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
71
+ }
72
+ </style>
73
+
74
+ <input
75
+ ref="control"
76
+ _aria-label="@label"
77
+ part="input"
78
+ _type="@type"
79
+ _value="@value"
80
+ _placeholder="@placeholder"
81
+ _autocomplete="@autocomplete"
82
+ _inputmode="@inputmode"
83
+ _pattern="@pattern"
84
+ _min="@min"
85
+ _max="@max"
86
+ _step="@step"
87
+ _minlength="@minlength"
88
+ _maxlength="@maxlength"
89
+ _disabled="@disabled"
90
+ _readonly="@readonly"
91
+ _required="@required"
92
+ _aria-invalid="@invalid"
93
+ >
94
+ </custom-element>
@@ -0,0 +1,208 @@
1
+ <custom-element name="ui-item">
2
+ <script>
3
+ defineProps({
4
+ variant: prop.enum(["default", "outline", "muted"]).default("default"),
5
+ size: prop.enum(["default", "sm"]).default("default"),
6
+ });
7
+
8
+ onConnected(() => {
9
+ internals.role = host.parentElement?.localName === "ui-item-group" ? "listitem" : null;
10
+ });
11
+ </script>
12
+
13
+ <style>
14
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
15
+ touch-action does not reach shadow content in iOS Safari. */
16
+ :host,
17
+ * {
18
+ touch-action: manipulation;
19
+ }
20
+ :host {
21
+ display: flex;
22
+ flex-wrap: wrap;
23
+ align-items: center;
24
+ gap: calc(var(--spacing) * 4);
25
+ padding: calc(var(--spacing) * 4);
26
+ border: 1px solid transparent;
27
+ border-radius: var(--radius-md);
28
+ font-size: var(--text-sm);
29
+ transition: background-color 0.1s;
30
+ }
31
+ :host(:state(attr-variant-is-outline)) {
32
+ border-color: var(--border);
33
+ }
34
+ :host(:state(attr-variant-is-muted)) {
35
+ background: color-mix(in oklab, var(--muted) 50%, transparent);
36
+ }
37
+ :host(:state(attr-size-is-sm)) {
38
+ gap: calc(var(--spacing) * 2.5);
39
+ padding: calc(var(--spacing) * 3) calc(var(--spacing) * 4);
40
+ }
41
+ </style>
42
+
43
+ <slot></slot>
44
+ </custom-element>
45
+
46
+ <custom-element name="ui-item-group" role="list">
47
+ <style>
48
+ :host,
49
+ * {
50
+ touch-action: manipulation;
51
+ }
52
+ :host {
53
+ display: flex;
54
+ flex-direction: column;
55
+ }
56
+ </style>
57
+
58
+ <slot></slot>
59
+ </custom-element>
60
+
61
+ <custom-element name="ui-item-media">
62
+ <style>
63
+ :host,
64
+ * {
65
+ touch-action: manipulation;
66
+ }
67
+ :host {
68
+ display: flex;
69
+ flex-shrink: 0;
70
+ align-items: center;
71
+ justify-content: center;
72
+ gap: calc(var(--spacing) * 2);
73
+ }
74
+ :host(:state(attr-variant-is-icon)) {
75
+ box-sizing: border-box;
76
+ width: calc(var(--spacing) * 8);
77
+ height: calc(var(--spacing) * 8);
78
+ border: 1px solid var(--border);
79
+ border-radius: var(--radius-sm);
80
+ background: var(--muted);
81
+ }
82
+ :host(:state(attr-variant-is-image)) {
83
+ width: calc(var(--spacing) * 10);
84
+ height: calc(var(--spacing) * 10);
85
+ overflow: hidden;
86
+ border-radius: var(--radius-sm);
87
+ }
88
+ ::slotted(svg) {
89
+ width: calc(var(--spacing) * 4);
90
+ height: calc(var(--spacing) * 4);
91
+ }
92
+ :host(:state(attr-variant-is-image)) ::slotted(img) {
93
+ width: 100%;
94
+ height: 100%;
95
+ object-fit: cover;
96
+ }
97
+ </style>
98
+
99
+ <slot></slot>
100
+ </custom-element>
101
+
102
+ <custom-element name="ui-item-content">
103
+ <style>
104
+ :host,
105
+ * {
106
+ touch-action: manipulation;
107
+ }
108
+ :host {
109
+ display: flex;
110
+ flex: 1;
111
+ flex-direction: column;
112
+ gap: calc(var(--spacing) * 1);
113
+ min-width: 0;
114
+ }
115
+ </style>
116
+
117
+ <slot></slot>
118
+ </custom-element>
119
+
120
+ <custom-element name="ui-item-title">
121
+ <style>
122
+ :host,
123
+ * {
124
+ touch-action: manipulation;
125
+ }
126
+ :host {
127
+ display: flex;
128
+ align-items: center;
129
+ gap: calc(var(--spacing) * 2);
130
+ font-weight: var(--font-weight-medium);
131
+ line-height: 1.25;
132
+ }
133
+ </style>
134
+
135
+ <slot></slot>
136
+ </custom-element>
137
+
138
+ <custom-element name="ui-item-description">
139
+ <style>
140
+ :host,
141
+ * {
142
+ touch-action: manipulation;
143
+ }
144
+ :host {
145
+ display: -webkit-box;
146
+ overflow: hidden;
147
+ -webkit-box-orient: vertical;
148
+ -webkit-line-clamp: 2;
149
+ color: var(--muted-foreground);
150
+ line-height: 1.4;
151
+ text-wrap: balance;
152
+ }
153
+ </style>
154
+
155
+ <slot></slot>
156
+ </custom-element>
157
+
158
+ <custom-element name="ui-item-actions">
159
+ <style>
160
+ :host,
161
+ * {
162
+ touch-action: manipulation;
163
+ }
164
+ :host {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: calc(var(--spacing) * 2);
168
+ }
169
+ </style>
170
+
171
+ <slot></slot>
172
+ </custom-element>
173
+
174
+ <custom-element name="ui-item-header">
175
+ <style>
176
+ :host,
177
+ * {
178
+ touch-action: manipulation;
179
+ }
180
+ :host {
181
+ display: flex;
182
+ flex-basis: 100%;
183
+ align-items: center;
184
+ justify-content: space-between;
185
+ gap: calc(var(--spacing) * 2);
186
+ }
187
+ </style>
188
+
189
+ <slot></slot>
190
+ </custom-element>
191
+
192
+ <custom-element name="ui-item-footer">
193
+ <style>
194
+ :host,
195
+ * {
196
+ touch-action: manipulation;
197
+ }
198
+ :host {
199
+ display: flex;
200
+ flex-basis: 100%;
201
+ align-items: center;
202
+ justify-content: space-between;
203
+ gap: calc(var(--spacing) * 2);
204
+ }
205
+ </style>
206
+
207
+ <slot></slot>
208
+ </custom-element>
@@ -0,0 +1,45 @@
1
+ <custom-element name="ui-kbd">
2
+ <style>
3
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
4
+ touch-action does not reach shadow content in iOS Safari. */
5
+ :host,
6
+ * {
7
+ touch-action: manipulation;
8
+ }
9
+ :host {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ gap: calc(var(--spacing) * 1);
14
+ height: calc(var(--spacing) * 5);
15
+ min-width: calc(var(--spacing) * 5);
16
+ padding: 0 calc(var(--spacing) * 1);
17
+ border-radius: var(--radius-xs);
18
+ background: var(--muted);
19
+ color: var(--muted-foreground);
20
+ font-family: var(--font-sans);
21
+ font-size: var(--text-xs);
22
+ font-weight: var(--font-weight-medium);
23
+ pointer-events: none;
24
+ user-select: none;
25
+ }
26
+ </style>
27
+
28
+ <slot></slot>
29
+ </custom-element>
30
+
31
+ <custom-element name="ui-kbd-group">
32
+ <style>
33
+ :host,
34
+ * {
35
+ touch-action: manipulation;
36
+ }
37
+ :host {
38
+ display: inline-flex;
39
+ align-items: center;
40
+ gap: calc(var(--spacing) * 1);
41
+ }
42
+ </style>
43
+
44
+ <slot></slot>
45
+ </custom-element>