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

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 +121 -0
  4. package/components/alert-dialog.html +196 -0
  5. package/components/alert.html +69 -0
  6. package/components/aspect-ratio.html +23 -0
  7. package/components/attachment.html +84 -0
  8. package/components/avatar.html +127 -0
  9. package/components/badge.html +56 -0
  10. package/components/breadcrumb.html +129 -0
  11. package/components/bubble.html +67 -0
  12. package/components/button-group.html +84 -0
  13. package/components/button.html +188 -0
  14. package/components/calendar.html +170 -0
  15. package/components/card.html +113 -0
  16. package/components/carousel.html +186 -0
  17. package/components/chart.html +162 -0
  18. package/components/checkbox.html +93 -0
  19. package/components/code-block.html +58 -0
  20. package/components/collapsible.html +76 -0
  21. package/components/combobox.html +224 -0
  22. package/components/command.html +189 -0
  23. package/components/context-menu.html +67 -0
  24. package/components/dialog.html +281 -0
  25. package/components/direction.html +17 -0
  26. package/components/drawer.html +224 -0
  27. package/components/dropdown-menu.html +372 -0
  28. package/components/empty.html +106 -0
  29. package/components/field.html +117 -0
  30. package/components/hover-card.html +94 -0
  31. package/components/input-group.html +75 -0
  32. package/components/input-otp.html +139 -0
  33. package/components/input.html +82 -0
  34. package/components/item.html +170 -0
  35. package/components/kbd.html +35 -0
  36. package/components/label.html +40 -0
  37. package/components/marker.html +35 -0
  38. package/components/menubar.html +116 -0
  39. package/components/message-scroller.html +62 -0
  40. package/components/message.html +77 -0
  41. package/components/navigation-menu.html +204 -0
  42. package/components/pagination.html +154 -0
  43. package/components/popover.html +133 -0
  44. package/components/progress.html +39 -0
  45. package/components/radio-group.html +114 -0
  46. package/components/resizable.html +156 -0
  47. package/components/scroll-area.html +50 -0
  48. package/components/select.html +91 -0
  49. package/components/separator.html +25 -0
  50. package/components/sheet.html +143 -0
  51. package/components/sidebar.html +343 -0
  52. package/components/skeleton.html +30 -0
  53. package/components/slider.html +81 -0
  54. package/components/spinner.html +47 -0
  55. package/components/switch.html +89 -0
  56. package/components/table.html +75 -0
  57. package/components/tabs.html +164 -0
  58. package/components/textarea.html +68 -0
  59. package/components/toaster.html +128 -0
  60. package/components/toggle-group.html +65 -0
  61. package/components/toggle.html +81 -0
  62. package/components/tooltip.html +97 -0
  63. package/dist/theme.css +145 -0
  64. package/dist/ui.html +6944 -0
  65. package/package.json +40 -3
  66. package/theme.css +145 -0
@@ -0,0 +1,113 @@
1
+ <custom-element name="ui-card">
2
+ <style>
3
+ :host {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: calc(var(--spacing) * 6);
7
+ padding: calc(var(--spacing) * 6) 0;
8
+ border: 1px solid var(--border);
9
+ border-radius: var(--radius-xl);
10
+ background: var(--card);
11
+ color: var(--card-foreground);
12
+ box-shadow: var(--shadow-xs);
13
+ }
14
+ </style>
15
+
16
+ <slot></slot>
17
+ </custom-element>
18
+
19
+ <custom-element name="ui-card-header">
20
+ <style>
21
+ :host {
22
+ display: grid;
23
+ grid-auto-rows: min-content;
24
+ align-items: start;
25
+ gap: calc(var(--spacing) * 1.5);
26
+ padding: 0 calc(var(--spacing) * 6);
27
+ }
28
+ ::slotted(:not(ui-card-action)) {
29
+ grid-column: 1;
30
+ }
31
+ </style>
32
+
33
+ <slot></slot>
34
+ </custom-element>
35
+
36
+ <custom-element name="ui-card-title">
37
+ <script>
38
+ defineProps({ level: prop.number().int().min(1).max(6).default(3) });
39
+ </script>
40
+
41
+ <style>
42
+ :host {
43
+ display: block;
44
+ }
45
+ [part="title"] {
46
+ margin: 0;
47
+ font-size: var(--text-base);
48
+ font-weight: var(--font-weight-semibold);
49
+ line-height: 1;
50
+ }
51
+ </style>
52
+
53
+ <div part="title" role="heading" _aria-level="@level"><slot></slot></div>
54
+ </custom-element>
55
+
56
+ <custom-element name="ui-card-description">
57
+ <style>
58
+ :host {
59
+ display: block;
60
+ font-size: var(--text-sm);
61
+ color: var(--muted-foreground);
62
+ }
63
+ </style>
64
+
65
+ <slot></slot>
66
+ </custom-element>
67
+
68
+ <custom-element name="ui-card-action">
69
+ <style>
70
+ :host {
71
+ display: block;
72
+ grid-column: 2;
73
+ grid-row: 1 / span 2;
74
+ justify-self: end;
75
+ }
76
+ </style>
77
+
78
+ <slot></slot>
79
+ </custom-element>
80
+
81
+ <custom-element name="ui-card-content">
82
+ <style>
83
+ :host {
84
+ display: block;
85
+ padding: 0 calc(var(--spacing) * 6);
86
+ }
87
+ </style>
88
+
89
+ <slot></slot>
90
+ </custom-element>
91
+
92
+ <custom-element name="ui-card-footer">
93
+ <style>
94
+ :host {
95
+ display: flex;
96
+ align-items: center;
97
+ gap: calc(var(--spacing) * 2);
98
+ padding: 0 calc(var(--spacing) * 6);
99
+ }
100
+ :host(:state(attr-justify-is-between)) {
101
+ justify-content: space-between;
102
+ }
103
+ :host(:state(attr-justify-is-end)) {
104
+ justify-content: flex-end;
105
+ }
106
+ :host(:state(attr-orientation-is-vertical)) {
107
+ flex-direction: column;
108
+ align-items: stretch;
109
+ }
110
+ </style>
111
+
112
+ <slot></slot>
113
+ </custom-element>
@@ -0,0 +1,186 @@
1
+ <import-element from="./button.html"></import-element>
2
+
3
+ <custom-element name="ui-carousel" role="region">
4
+ <script>
5
+ const props = defineProps({
6
+ orientation: prop.enum(["horizontal", "vertical"]).default("horizontal"),
7
+ label: prop.string().default("Carousel"),
8
+ });
9
+
10
+ const content = useChildren("ui-carousel-content");
11
+ const position = useState({
12
+ index: 0,
13
+ get first() {
14
+ return this.index <= 0;
15
+ },
16
+ get last() {
17
+ return this.index >= slides().length - 1;
18
+ },
19
+ });
20
+ const KEYS = { ArrowLeft: -1, ArrowUp: -1, ArrowRight: 1, ArrowDown: 1 };
21
+
22
+ function slides() {
23
+ return [...(content[0]?.children ?? [])];
24
+ }
25
+
26
+ function settle(index) {
27
+ if (index === position.index) return;
28
+ position.index = index;
29
+ dispatch("change", { index });
30
+ }
31
+
32
+ function go(index) {
33
+ const all = slides();
34
+ const target = Math.max(0, Math.min(index, all.length - 1));
35
+ all[target]?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
36
+ settle(target);
37
+ }
38
+
39
+ function previous() {
40
+ go(position.index - 1);
41
+ }
42
+
43
+ function next() {
44
+ go(position.index + 1);
45
+ }
46
+
47
+ function keys(event) {
48
+ const step = KEYS[event.key];
49
+ if (!step) return;
50
+ event.preventDefault();
51
+ go(position.index + step);
52
+ }
53
+
54
+ function scrolled(track) {
55
+ const edge = props.orientation === "vertical" ? "top" : "left";
56
+ const start = track.getBoundingClientRect()[edge];
57
+ const distances = slides().map((slide) => Math.abs(slide.getBoundingClientRect()[edge] - start));
58
+ settle(distances.indexOf(Math.min(...distances)));
59
+ }
60
+
61
+ observe(() => {
62
+ internals.ariaLabel = props.label;
63
+ internals.ariaRoleDescription = "carousel";
64
+ });
65
+ </script>
66
+
67
+ <style>
68
+ :host {
69
+ --carousel-direction: row;
70
+ --carousel-axis: x;
71
+ --carousel-overflow-x: auto;
72
+ --carousel-overflow-y: hidden;
73
+ display: grid;
74
+ grid-template-columns: auto minmax(0, 1fr) auto;
75
+ align-items: center;
76
+ width: 100%;
77
+ }
78
+ :host(:state(attr-orientation-is-vertical)) {
79
+ --carousel-direction: column;
80
+ --carousel-axis: y;
81
+ --carousel-overflow-x: hidden;
82
+ --carousel-overflow-y: auto;
83
+ grid-template-columns: minmax(0, 1fr);
84
+ grid-template-rows: auto minmax(0, 1fr) auto;
85
+ justify-items: center;
86
+ }
87
+ :host(:state(attr-height)) {
88
+ height: calc(var(--spacing) * var(--attr-height));
89
+ }
90
+ ::slotted(ui-carousel-content) {
91
+ grid-area: 1 / 2;
92
+ align-self: stretch;
93
+ }
94
+ :host(:state(attr-orientation-is-vertical)) ::slotted(ui-carousel-content) {
95
+ grid-area: 2 / 1;
96
+ justify-self: stretch;
97
+ }
98
+ ::slotted(ui-carousel-previous) {
99
+ grid-area: 1 / 1;
100
+ margin-inline-end: calc(var(--spacing) * 2);
101
+ }
102
+ ::slotted(ui-carousel-next) {
103
+ grid-area: 1 / 3;
104
+ margin-inline-start: calc(var(--spacing) * 2);
105
+ }
106
+ :host(:state(attr-orientation-is-vertical)) ::slotted(ui-carousel-previous) {
107
+ grid-area: 1 / 1;
108
+ margin: 0 0 calc(var(--spacing) * 2);
109
+ rotate: 90deg;
110
+ }
111
+ :host(:state(attr-orientation-is-vertical)) ::slotted(ui-carousel-next) {
112
+ grid-area: 3 / 1;
113
+ margin: calc(var(--spacing) * 2) 0 0;
114
+ rotate: 90deg;
115
+ }
116
+ </style>
117
+
118
+ <slot></slot>
119
+ </custom-element>
120
+
121
+ <custom-element name="ui-carousel-content">
122
+ <style>
123
+ :host {
124
+ display: flex;
125
+ min-width: 0;
126
+ min-height: 0;
127
+ }
128
+ .track {
129
+ display: flex;
130
+ flex: 1;
131
+ flex-direction: var(--carousel-direction);
132
+ gap: calc(var(--spacing) * 4);
133
+ min-width: 0;
134
+ min-height: 0;
135
+ overflow-x: var(--carousel-overflow-x);
136
+ overflow-y: var(--carousel-overflow-y);
137
+ scroll-snap-type: var(--carousel-axis) mandatory;
138
+ scrollbar-width: none;
139
+ outline: none;
140
+ }
141
+ .track:focus-visible {
142
+ box-shadow: var(--focus-ring);
143
+ }
144
+ </style>
145
+
146
+ <div class="track" part="track" tabindex="0" onkeydown="keys(event)" onscrollend="scrolled(this)"><slot></slot></div>
147
+ </custom-element>
148
+
149
+ <custom-element name="ui-carousel-item" role="group">
150
+ <script>
151
+ internals.ariaRoleDescription = "slide";
152
+ </script>
153
+
154
+ <style>
155
+ :host {
156
+ display: block;
157
+ box-sizing: border-box;
158
+ flex: 0 0 100%;
159
+ min-width: 0;
160
+ min-height: 0;
161
+ scroll-snap-align: start;
162
+ }
163
+ </style>
164
+
165
+ <slot></slot>
166
+ </custom-element>
167
+
168
+ <custom-element name="ui-carousel-previous">
169
+ <style>
170
+ :host {
171
+ display: inline-flex;
172
+ }
173
+ </style>
174
+
175
+ <ui-button variant="outline" size="icon" aria-label="Previous slide" _disabled="#position.first" onclick="previous()">‹</ui-button>
176
+ </custom-element>
177
+
178
+ <custom-element name="ui-carousel-next">
179
+ <style>
180
+ :host {
181
+ display: inline-flex;
182
+ }
183
+ </style>
184
+
185
+ <ui-button variant="outline" size="icon" aria-label="Next slide" _disabled="#position.last" onclick="next()">›</ui-button>
186
+ </custom-element>
@@ -0,0 +1,162 @@
1
+ <custom-element name="ui-chart">
2
+ <script>
3
+ const props = defineProps({
4
+ type: prop.enum(["bar", "line", "area"]).default("bar"),
5
+ data: prop.array(prop.object()).default([]),
6
+ x: prop.string().default("label"),
7
+ y: prop.string().default("value"),
8
+ height: prop.number().min(80).default(240),
9
+ label: prop.string().default("Chart"),
10
+ });
11
+
12
+ const WIDTH = 600;
13
+ const PAD = { top: 12, right: 12, bottom: 28, left: 44 };
14
+
15
+ const chart = useState({
16
+ get series() {
17
+ return props.y.split(",").map((key, index) => ({ key: key.trim(), color: `var(--chart-${(index % 5) + 1})` }));
18
+ },
19
+ get values() {
20
+ return props.data.flatMap((row) => this.series.map((series) => Number(row[series.key]) || 0));
21
+ },
22
+ get max() {
23
+ return Math.max(1, ...this.values);
24
+ },
25
+ get min() {
26
+ return Math.min(0, ...this.values);
27
+ },
28
+ get plotHeight() {
29
+ return props.height - PAD.top - PAD.bottom;
30
+ },
31
+ y(value) {
32
+ return PAD.top + this.plotHeight * ((this.max - value) / (this.max - this.min));
33
+ },
34
+ get step() {
35
+ return (WIDTH - PAD.left - PAD.right) / Math.max(1, props.data.length);
36
+ },
37
+ get viewBox() {
38
+ return `0 0 ${WIDTH} ${props.height}`;
39
+ },
40
+ get ticks() {
41
+ return [0, 0.25, 0.5, 0.75, 1].map((ratio) => ({
42
+ key: ratio,
43
+ y: PAD.top + this.plotHeight * (1 - ratio),
44
+ label: Math.round(this.min + (this.max - this.min) * ratio).toLocaleString(),
45
+ }));
46
+ },
47
+ get labels() {
48
+ return props.data.map((row, index) => ({ key: index, x: PAD.left + this.step * (index + 0.5), text: String(row[props.x] ?? "") }));
49
+ },
50
+ get bars() {
51
+ const width = (this.step * 0.7) / this.series.length;
52
+ return props.data.flatMap((row, index) =>
53
+ this.series.map((series, s) => {
54
+ const value = Number(row[series.key]) || 0;
55
+ const top = this.y(Math.max(value, 0));
56
+ return {
57
+ key: `${index}-${series.key}`,
58
+ x: PAD.left + this.step * index + this.step * 0.15 + width * s,
59
+ y: top,
60
+ width,
61
+ height: this.y(Math.min(value, 0)) - top,
62
+ color: series.color,
63
+ title: `${row[props.x]} · ${series.key}: ${value.toLocaleString()}`,
64
+ };
65
+ }),
66
+ );
67
+ },
68
+ get lines() {
69
+ return this.series.map((series) => {
70
+ const points = props.data.map((row, index) => [
71
+ PAD.left + this.step * (index + 0.5),
72
+ this.y(Number(row[series.key]) || 0),
73
+ ]);
74
+ const line = points.map(([x, y], index) => `${index ? "L" : "M"}${x},${y}`).join(" ");
75
+ const zero = this.y(0);
76
+ const area = points.length ? `${line} L${points.at(-1)[0]},${zero} L${points[0][0]},${zero} Z` : "";
77
+ return { key: series.key, line, area, color: series.color };
78
+ });
79
+ },
80
+ get legend() {
81
+ return this.series.length > 1 ? this.series : [];
82
+ },
83
+ });
84
+ </script>
85
+
86
+ <style>
87
+ :host {
88
+ display: block;
89
+ width: 100%;
90
+ box-sizing: border-box;
91
+ font-size: var(--text-xs);
92
+ color: var(--muted-foreground);
93
+ }
94
+ svg {
95
+ display: block;
96
+ width: 100%;
97
+ height: auto;
98
+ overflow: visible;
99
+ }
100
+ .grid {
101
+ stroke: var(--border);
102
+ stroke-dasharray: 3 3;
103
+ }
104
+ .tick {
105
+ fill: currentColor;
106
+ text-anchor: end;
107
+ dominant-baseline: middle;
108
+ }
109
+ .label {
110
+ fill: currentColor;
111
+ text-anchor: middle;
112
+ }
113
+ rect {
114
+ rx: 4;
115
+ }
116
+ .line {
117
+ fill: none;
118
+ stroke-width: 2;
119
+ }
120
+ .area {
121
+ opacity: 0.2;
122
+ }
123
+ :host(:not(:state(attr-type-is-bar))) .bars,
124
+ :host(:state(attr-type-is-bar)) .lines,
125
+ :host(:not(:state(attr-type-is-area))) .area {
126
+ display: none;
127
+ }
128
+ .legend {
129
+ display: flex;
130
+ justify-content: center;
131
+ gap: calc(var(--spacing) * 4);
132
+ margin-top: calc(var(--spacing) * 2);
133
+ }
134
+ .legend span::before {
135
+ content: "";
136
+ display: inline-block;
137
+ width: calc(var(--spacing) * 2.5);
138
+ height: calc(var(--spacing) * 2.5);
139
+ margin-right: calc(var(--spacing) * 1.5);
140
+ border-radius: var(--radius-xs);
141
+ background: var(--swatch);
142
+ }
143
+ </style>
144
+
145
+ <svg _viewBox="#chart.viewBox" role="img" _aria-label="@label" part="svg">
146
+ <g repeat="#chart.ticks" as="tick" key="key">
147
+ <line class="grid" x1="44" x2="588" _y1="#tick.y" _y2="#tick.y"></line>
148
+ <text class="tick" x="38" _y="#tick.y"><slot name="#tick.label"></slot></text>
149
+ </g>
150
+ <text repeat="#chart.labels" as="label" key="key" class="label" _x="#label.x" _y="@height" dy="-8"><slot name="#label.text"></slot></text>
151
+ <g class="bars">
152
+ <rect repeat="#chart.bars" as="bar" key="key" _x="#bar.x" _y="#bar.y" _width="#bar.width" _height="#bar.height" _fill="#bar.color"><title><slot name="#bar.title"></slot></title></rect>
153
+ </g>
154
+ <g class="lines" repeat="#chart.lines" as="series" key="key">
155
+ <path class="area" _d="#series.area" _fill="#series.color"></path>
156
+ <path class="line" _d="#series.line" _stroke="#series.color"></path>
157
+ </g>
158
+ </svg>
159
+ <div class="legend" part="legend">
160
+ <span repeat="#chart.legend" as="series" key="key" _--swatch="#series.color"><slot name="#series.key"></slot></span>
161
+ </div>
162
+ </custom-element>
@@ -0,0 +1,93 @@
1
+ <custom-element name="ui-checkbox" form-associated delegates-focus>
2
+ <script>
3
+ const props = defineProps({
4
+ checked: prop.boolean(),
5
+ value: prop.string(),
6
+ indeterminate: prop.boolean(),
7
+ disabled: prop.boolean(),
8
+ required: prop.boolean(),
9
+ label: prop.string().attribute("aria-label"),
10
+ });
11
+
12
+ function settled() {
13
+ props.indeterminate = false;
14
+ }
15
+ </script>
16
+
17
+ <style>
18
+ :host(:state(attr-checked)) label {
19
+ color: var(--foreground);
20
+ }
21
+ :host {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ gap: calc(var(--spacing) * 2);
25
+ vertical-align: middle;
26
+ }
27
+ input {
28
+ appearance: none;
29
+ box-sizing: border-box;
30
+ display: grid;
31
+ place-content: center;
32
+ width: calc(var(--spacing) * 4);
33
+ height: calc(var(--spacing) * 4);
34
+ margin: 0;
35
+ border: 1px solid var(--input);
36
+ border-radius: var(--radius-xs);
37
+ background: transparent;
38
+ box-shadow: var(--shadow-xs);
39
+ cursor: pointer;
40
+ outline: none;
41
+ transition: box-shadow var(--default-transition-duration);
42
+ }
43
+ input::before {
44
+ content: "";
45
+ width: calc(var(--spacing) * 2.5);
46
+ height: calc(var(--spacing) * 2.5);
47
+ background: currentColor;
48
+ clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
49
+ transform: scale(0);
50
+ transition: transform 0.1s;
51
+ }
52
+ input:checked,
53
+ input:indeterminate {
54
+ background: var(--primary);
55
+ border-color: var(--primary);
56
+ color: var(--primary-foreground);
57
+ }
58
+ input:checked::before {
59
+ transform: scale(1);
60
+ }
61
+ input:indeterminate::before {
62
+ clip-path: inset(40% 0);
63
+ transform: scale(1);
64
+ }
65
+ input:focus-visible {
66
+ border-color: var(--ring);
67
+ box-shadow: var(--focus-ring);
68
+ }
69
+ input:disabled {
70
+ cursor: not-allowed;
71
+ opacity: 0.5;
72
+ }
73
+ label {
74
+ font-size: var(--text-sm);
75
+ font-weight: var(--font-weight-medium);
76
+ line-height: 1;
77
+ }
78
+ </style>
79
+
80
+ <input
81
+ id="control"
82
+ ref="control"
83
+ part="control"
84
+ type="checkbox"
85
+ _aria-label="@label"
86
+ _checked="@checked"
87
+ _indeterminate="@indeterminate"
88
+ _disabled="@disabled"
89
+ _required="@required"
90
+ onchange="settled()"
91
+ >
92
+ <label for="control" part="label"><slot></slot></label>
93
+ </custom-element>
@@ -0,0 +1,58 @@
1
+ <import-element from="./button.html"></import-element>
2
+
3
+ <custom-element name="ui-code-block">
4
+ <script>
5
+ defineProps({ language: prop.string() });
6
+
7
+ const copy = useState({ done: false });
8
+
9
+ async function copyCode() {
10
+ await navigator.clipboard.writeText(host.textContent.trim());
11
+ copy.done = true;
12
+ setTimeout(() => (copy.done = false), 1500);
13
+ }
14
+ </script>
15
+
16
+ <style>
17
+ :host {
18
+ position: relative;
19
+ display: block;
20
+ width: 100%;
21
+ box-sizing: border-box;
22
+ border: 1px solid var(--border);
23
+ border-radius: var(--radius-lg);
24
+ background: color-mix(in oklab, var(--muted) 40%, transparent);
25
+ font-family: var(--font-mono);
26
+ font-size: var(--text-sm);
27
+ }
28
+ .code {
29
+ padding: calc(var(--spacing) * 4) calc(var(--spacing) * 5);
30
+ overflow-x: auto;
31
+ white-space: pre;
32
+ }
33
+ ::slotted(pre) {
34
+ margin: 0;
35
+ }
36
+ .bar {
37
+ position: absolute;
38
+ top: calc(var(--spacing) * 1.5);
39
+ right: calc(var(--spacing) * 1.5);
40
+ display: flex;
41
+ align-items: center;
42
+ gap: calc(var(--spacing) * 2);
43
+ font-family: var(--font-sans);
44
+ font-size: var(--text-xs);
45
+ text-transform: uppercase;
46
+ color: var(--muted-foreground);
47
+ }
48
+ </style>
49
+
50
+ <div class="code" part="code"><slot></slot></div>
51
+ <div class="bar" part="bar">
52
+ <span><slot name="@language"></slot></span>
53
+ <ui-button variant="ghost" size="icon-sm" aria-label="Copy code" onclick="copyCode()">
54
+ <is-hidden when="#copy.done">⧉</is-hidden>
55
+ <is-visible when="#copy.done">✓</is-visible>
56
+ </ui-button>
57
+ </div>
58
+ </custom-element>
@@ -0,0 +1,76 @@
1
+ <custom-element name="ui-collapsible">
2
+ <script>
3
+ const props = defineProps({ open: prop.boolean().reflect(), disabled: prop.boolean() });
4
+
5
+ function toggled(details) {
6
+ if (details.open === props.open) return;
7
+ props.open = details.open;
8
+ dispatch("toggle");
9
+ }
10
+
11
+ function guard() {
12
+ return !props.disabled;
13
+ }
14
+ </script>
15
+
16
+ <style>
17
+ :host {
18
+ display: block;
19
+ }
20
+ summary {
21
+ list-style: none;
22
+ border-radius: var(--radius-md);
23
+ cursor: pointer;
24
+ outline: none;
25
+ }
26
+ summary::-webkit-details-marker {
27
+ display: none;
28
+ }
29
+ summary:focus-visible {
30
+ box-shadow: var(--focus-ring);
31
+ }
32
+ :host(:state(attr-disabled)) summary {
33
+ pointer-events: none;
34
+ opacity: 0.5;
35
+ }
36
+ </style>
37
+
38
+ <details _open="@open" ontoggle="toggled(this)">
39
+ <summary part="trigger" onclick="return guard()"><slot name="trigger"></slot></summary>
40
+ <slot></slot>
41
+ </details>
42
+ </custom-element>
43
+
44
+ <custom-element name="ui-collapsible-trigger" slot="trigger">
45
+ <style>
46
+ :host {
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: space-between;
50
+ gap: calc(var(--spacing) * 4);
51
+ font-size: var(--text-sm);
52
+ font-weight: var(--font-weight-semibold);
53
+ }
54
+ ::slotted(svg) {
55
+ flex-shrink: 0;
56
+ width: calc(var(--spacing) * 4);
57
+ height: calc(var(--spacing) * 4);
58
+ }
59
+ </style>
60
+
61
+ <slot></slot>
62
+ </custom-element>
63
+
64
+ <custom-element name="ui-collapsible-content">
65
+ <style>
66
+ :host {
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: calc(var(--spacing) * 2);
70
+ padding-top: calc(var(--spacing) * 2);
71
+ font-size: var(--text-sm);
72
+ }
73
+ </style>
74
+
75
+ <slot></slot>
76
+ </custom-element>