@relements/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/base.css +1 -0
  2. package/dist/behaviors/dialog.d.ts +39 -0
  3. package/dist/behaviors/dialog.js +1 -0
  4. package/dist/behaviors/dismissible.d.ts +37 -0
  5. package/dist/behaviors/dismissible.js +1 -0
  6. package/dist/behaviors/menu-button.d.ts +36 -0
  7. package/dist/behaviors/menu-button.js +1 -0
  8. package/dist/behaviors/popover.d.ts +28 -0
  9. package/dist/behaviors/popover.js +1 -0
  10. package/dist/behaviors/tabs.d.ts +37 -0
  11. package/dist/behaviors/tabs.js +1 -0
  12. package/dist/behaviors/toast.d.ts +42 -0
  13. package/dist/behaviors/toast.js +1 -0
  14. package/dist/chunk-GMICGIQW.js +149 -0
  15. package/dist/chunk-J4EGUBPP.js +68 -0
  16. package/dist/chunk-PIDPGDBZ.js +62 -0
  17. package/dist/chunk-PSODVT3V.js +67 -0
  18. package/dist/chunk-TC4TFP7Y.js +40 -0
  19. package/dist/chunk-ZHRJNWMH.js +174 -0
  20. package/dist/components/button.css +1 -0
  21. package/dist/components/dialog.css +1 -0
  22. package/dist/components/disclosure.css +1 -0
  23. package/dist/components/form.css +1 -0
  24. package/dist/components/link.css +1 -0
  25. package/dist/components/menu.css +1 -0
  26. package/dist/components/popover.css +1 -0
  27. package/dist/components/progress.css +1 -0
  28. package/dist/components/tabs.css +1 -0
  29. package/dist/components/toast.css +1 -0
  30. package/dist/elements/re-menu.d.ts +10 -0
  31. package/dist/elements/re-menu.js +36 -0
  32. package/dist/elements/re-popover.d.ts +12 -0
  33. package/dist/elements/re-popover.js +35 -0
  34. package/dist/elements/re-tabs.d.ts +20 -0
  35. package/dist/elements/re-tabs.js +60 -0
  36. package/dist/elements/re-toast.d.ts +15 -0
  37. package/dist/elements/re-toast.js +30 -0
  38. package/dist/index.css +1 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.js +6 -0
  41. package/dist/reset.css +1 -0
  42. package/dist/themes/renascent.css +1 -0
  43. package/dist/tokens.css +1 -0
  44. package/package.json +84 -0
  45. package/src/base.css +129 -0
  46. package/src/behaviors/dialog.js +106 -0
  47. package/src/behaviors/dismissible.js +68 -0
  48. package/src/behaviors/menu-button.js +199 -0
  49. package/src/behaviors/popover.js +103 -0
  50. package/src/behaviors/tabs.js +171 -0
  51. package/src/behaviors/toast.js +97 -0
  52. package/src/components/button.css +141 -0
  53. package/src/components/dialog.css +106 -0
  54. package/src/components/disclosure.css +83 -0
  55. package/src/components/form.css +334 -0
  56. package/src/components/link.css +61 -0
  57. package/src/components/menu.css +78 -0
  58. package/src/components/popover.css +50 -0
  59. package/src/components/progress.css +112 -0
  60. package/src/components/tabs.css +86 -0
  61. package/src/components/toast.css +87 -0
  62. package/src/elements/re-menu.js +54 -0
  63. package/src/elements/re-popover.js +59 -0
  64. package/src/elements/re-tabs.js +92 -0
  65. package/src/elements/re-toast.js +46 -0
  66. package/src/index.css +30 -0
  67. package/src/index.js +13 -0
  68. package/src/reset.css +103 -0
  69. package/src/themes/renascent.css +198 -0
  70. package/src/tokens.css +196 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * enhanceTabs
3
+ * -----------
4
+ * Wires the ARIA tabs pattern over server-rendered markup.
5
+ *
6
+ * <div class="re-tabs" data-re-tabs>
7
+ * <div role="tablist" aria-label="Settings">
8
+ * <button role="tab" id="t-1" aria-controls="p-1" aria-selected="true">…</button>
9
+ * <button role="tab" id="t-2" aria-controls="p-2" aria-selected="false" tabindex="-1">…</button>
10
+ * </div>
11
+ * <section role="tabpanel" id="p-1" aria-labelledby="t-1">…</section>
12
+ * <section role="tabpanel" id="p-2" aria-labelledby="t-2" hidden>…</section>
13
+ * </div>
14
+ *
15
+ * Keyboard:
16
+ * ArrowLeft / ArrowRight — move focus across tabs (roving tabindex)
17
+ * Home / End — jump to first / last
18
+ * Enter / Space — activate focused tab (manual activation)
19
+ *
20
+ * Dispatches `re-change` (bubbles, cancelable) on the host `[data-re-tabs]`
21
+ * with `detail = { tabId, panelId }` whenever the selected tab changes.
22
+ *
23
+ * import { enhanceTabs } from "@relements/core/behaviors/tabs";
24
+ * const c = enhanceTabs(document);
25
+ * c.destroy();
26
+ */
27
+
28
+ /** @typedef {{ destroy: () => void }} Controller */
29
+
30
+ /**
31
+ * @param {Document | Element | ShadowRoot} [root=document]
32
+ * @returns {Controller}
33
+ */
34
+ export function enhanceTabs(root = document) {
35
+ if (root == null) {
36
+ throw new TypeError("enhanceTabs: root must be a Document, Element, or ShadowRoot");
37
+ }
38
+
39
+ /** @type {Array<{ host: Element; cleanup: () => void }>} */
40
+ const hosts = [];
41
+
42
+ // Include the root itself when it carries the marker (e.g. a custom element host).
43
+ if (root instanceof Element && /** @type {Element} */ (root).matches?.("[data-re-tabs]")) {
44
+ const cleanup = wireOne(/** @type {HTMLElement} */ (root));
45
+ hosts.push({ host: root, cleanup });
46
+ }
47
+
48
+ /** @type {NodeListOf<Element>} */
49
+ const tabsList = root.querySelectorAll("[data-re-tabs]");
50
+ tabsList.forEach((host) => {
51
+ const cleanup = wireOne(/** @type {HTMLElement} */ (host));
52
+ hosts.push({ host, cleanup });
53
+ });
54
+
55
+ return {
56
+ destroy() {
57
+ while (hosts.length) {
58
+ const entry = hosts.pop();
59
+ entry?.cleanup();
60
+ }
61
+ },
62
+ };
63
+ }
64
+
65
+ /**
66
+ * @param {HTMLElement} host
67
+ * @returns {() => void}
68
+ */
69
+ function wireOne(host) {
70
+ const tablist = host.querySelector('[role="tablist"]');
71
+ if (!tablist) return () => {};
72
+ /** @type {HTMLElement[]} */
73
+ const tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
74
+ if (tabs.length === 0) return () => {};
75
+
76
+ // Ensure roving tabindex matches aria-selected.
77
+ const syncRoving = () => {
78
+ for (const t of tabs) {
79
+ t.tabIndex = t.getAttribute("aria-selected") === "true" ? 0 : -1;
80
+ }
81
+ };
82
+ syncRoving();
83
+
84
+ /**
85
+ * @param {HTMLElement} tab
86
+ * @param {{ focus?: boolean }} [opts]
87
+ */
88
+ const select = (tab, opts = {}) => {
89
+ if (tab.getAttribute("aria-selected") === "true") {
90
+ if (opts.focus) tab.focus();
91
+ return;
92
+ }
93
+ const panelId = tab.getAttribute("aria-controls");
94
+ const cancelled = !host.dispatchEvent(
95
+ new CustomEvent("re-change", {
96
+ bubbles: true,
97
+ cancelable: true,
98
+ detail: { tabId: tab.id, panelId },
99
+ }),
100
+ );
101
+ if (cancelled) return;
102
+
103
+ for (const t of tabs) {
104
+ const isMe = t === tab;
105
+ t.setAttribute("aria-selected", isMe ? "true" : "false");
106
+ t.tabIndex = isMe ? 0 : -1;
107
+ const pid = t.getAttribute("aria-controls");
108
+ if (pid) {
109
+ const panel = host.querySelector(`#${cssEscape(pid)}`);
110
+ if (panel) /** @type {HTMLElement} */ (panel).hidden = !isMe;
111
+ }
112
+ }
113
+ if (opts.focus) tab.focus();
114
+ };
115
+
116
+ /** @param {Event} event */
117
+ const onClick = (event) => {
118
+ const t = /** @type {Element | null} */ (event.target);
119
+ const tab = t?.closest('[role="tab"]');
120
+ if (tab && tabs.includes(/** @type {HTMLElement} */ (tab))) {
121
+ select(/** @type {HTMLElement} */ (tab), { focus: true });
122
+ }
123
+ };
124
+
125
+ /** @param {KeyboardEvent} event */
126
+ const onKey = (event) => {
127
+ const current = document.activeElement;
128
+ if (!current || !tabs.includes(/** @type {HTMLElement} */ (current))) return;
129
+ const idx = tabs.indexOf(/** @type {HTMLElement} */ (current));
130
+ if (idx === -1) return;
131
+ let nextIdx = idx;
132
+ switch (event.key) {
133
+ case "ArrowRight":
134
+ nextIdx = (idx + 1) % tabs.length;
135
+ break;
136
+ case "ArrowLeft":
137
+ nextIdx = (idx - 1 + tabs.length) % tabs.length;
138
+ break;
139
+ case "Home":
140
+ nextIdx = 0;
141
+ break;
142
+ case "End":
143
+ nextIdx = tabs.length - 1;
144
+ break;
145
+ case "Enter":
146
+ case " ":
147
+ case "Spacebar":
148
+ event.preventDefault();
149
+ select(/** @type {HTMLElement} */ (current), { focus: true });
150
+ return;
151
+ default:
152
+ return;
153
+ }
154
+ event.preventDefault();
155
+ tabs[nextIdx].focus();
156
+ select(tabs[nextIdx]);
157
+ };
158
+
159
+ tablist.addEventListener("click", onClick);
160
+ tablist.addEventListener("keydown", /** @type {EventListener} */ (onKey));
161
+
162
+ return () => {
163
+ tablist.removeEventListener("click", onClick);
164
+ tablist.removeEventListener("keydown", /** @type {EventListener} */ (onKey));
165
+ };
166
+ }
167
+
168
+ /** @param {string} value */
169
+ function cssEscape(value) {
170
+ return typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value;
171
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Toast helpers.
3
+ *
4
+ * <div class="re-toast-region" data-re-toast-region role="region" aria-label="Notifications">
5
+ * <ul class="re-toast-list" aria-live="polite" aria-relevant="additions"></ul>
6
+ * </div>
7
+ *
8
+ * import { showToast } from "@relements/core/behaviors/toast";
9
+ * showToast("Saved", { tone: "success" });
10
+ * showToast("Network error", { tone: "danger", duration: 8000 });
11
+ *
12
+ * If no `[data-re-toast-region]` exists, one is created and appended to
13
+ * `document.body` on first call.
14
+ */
15
+
16
+ /**
17
+ * @typedef {object} ToastOptions
18
+ * @property {"default"|"success"|"warning"|"danger"} [tone="default"]
19
+ * @property {number} [duration=4000] Auto-dismiss in ms. Pass 0 to disable.
20
+ * @property {Document|Element} [root] Override the host to search for a region in.
21
+ */
22
+
23
+ /**
24
+ * @param {string} message
25
+ * @param {ToastOptions} [options]
26
+ * @returns {{ dismiss: () => void; element: HTMLDivElement }}
27
+ */
28
+ export function showToast(message, options = {}) {
29
+ const { tone = "default", duration = 4000, root = document } = options;
30
+
31
+ const list = ensureRegion(root).querySelector(".re-toast-list");
32
+ if (!list) {
33
+ throw new Error("showToast: toast region missing a `.re-toast-list`");
34
+ }
35
+
36
+ const item = document.createElement("div");
37
+ item.className = "re-toast";
38
+ if (tone !== "default") item.dataset.tone = tone;
39
+ item.setAttribute("role", tone === "danger" ? "alert" : "status");
40
+
41
+ const body = document.createElement("div");
42
+ body.className = "re-toast__body";
43
+ body.textContent = message;
44
+ item.appendChild(body);
45
+
46
+ const dismissBtn = document.createElement("button");
47
+ dismissBtn.type = "button";
48
+ dismissBtn.className = "re-toast__dismiss";
49
+ dismissBtn.setAttribute("aria-label", "Dismiss notification");
50
+ dismissBtn.textContent = "×";
51
+ item.appendChild(dismissBtn);
52
+
53
+ list.appendChild(item);
54
+
55
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
56
+ let timer;
57
+ const dismiss = () => {
58
+ if (timer != null) {
59
+ clearTimeout(timer);
60
+ timer = undefined;
61
+ }
62
+ item.dispatchEvent(new CustomEvent("re-toast-dismiss", { bubbles: true }));
63
+ item.remove();
64
+ };
65
+
66
+ dismissBtn.addEventListener("click", dismiss);
67
+
68
+ if (duration > 0) {
69
+ timer = setTimeout(dismiss, duration);
70
+ }
71
+
72
+ return { dismiss, element: item };
73
+ }
74
+
75
+ /**
76
+ * @param {Document|Element} root
77
+ * @returns {HTMLElement}
78
+ */
79
+ function ensureRegion(root) {
80
+ const scope =
81
+ root instanceof Document ? root : /** @type {Element} */ (root.ownerDocument ?? document);
82
+ /** @type {HTMLElement | null} */
83
+ let region = /** @type {HTMLElement | null} */ (scope.querySelector("[data-re-toast-region]"));
84
+ if (region) return region;
85
+ region = scope.createElement("div");
86
+ region.className = "re-toast-region";
87
+ region.setAttribute("role", "region");
88
+ region.setAttribute("aria-label", "Notifications");
89
+ region.dataset.reToastRegion = "";
90
+ const list = scope.createElement("div");
91
+ list.className = "re-toast-list";
92
+ list.setAttribute("aria-live", "polite");
93
+ list.setAttribute("aria-relevant", "additions");
94
+ region.appendChild(list);
95
+ scope.body.appendChild(region);
96
+ return region;
97
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Relements button.
3
+ *
4
+ * Applies to <button>, <a class="re-button">, and <input type="submit|button|reset">.
5
+ * Native semantics stay intact — this is purely styling on top of the host element.
6
+ *
7
+ * Variants (data-variant):
8
+ * primary — accent fill, white text. Default if data-variant absent.
9
+ * secondary — outlined neutral
10
+ * ghost — transparent, neutral text, no border
11
+ * danger — danger fill
12
+ *
13
+ * Sizes (data-size):
14
+ * sm, md (default), lg
15
+ *
16
+ * States:
17
+ * :disabled, :hover, :active, :focus-visible, [aria-disabled="true"]
18
+ */
19
+
20
+ @layer re.components {
21
+ .re-button {
22
+ /* token-bound layout */
23
+ display: inline-flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ gap: var(--re-space-2);
27
+ box-sizing: border-box;
28
+
29
+ /* default = primary md */
30
+ block-size: var(--re-control-height-md);
31
+ padding-inline: var(--re-space-4);
32
+ padding-block: 0;
33
+
34
+ /* type */
35
+ font: inherit;
36
+ font-weight: var(--re-font-weight-medium);
37
+ font-size: var(--re-size-text-md);
38
+ line-height: 1;
39
+ text-align: center;
40
+ text-decoration: none;
41
+ white-space: nowrap;
42
+ user-select: none;
43
+
44
+ /* surface */
45
+ color: var(--re-color-text-on-accent);
46
+ background-color: var(--re-color-accent-600);
47
+ border: var(--re-border-width) solid transparent;
48
+ border-radius: var(--re-radius-md);
49
+ cursor: pointer;
50
+
51
+ transition:
52
+ background-color var(--re-duration-fast) var(--re-easing-standard),
53
+ color var(--re-duration-fast) var(--re-easing-standard),
54
+ border-color var(--re-duration-fast) var(--re-easing-standard),
55
+ box-shadow var(--re-duration-fast) var(--re-easing-standard);
56
+ }
57
+
58
+ /* Make link-as-button visually identical and behave on tap. */
59
+ a.re-button {
60
+ -webkit-tap-highlight-color: transparent;
61
+ }
62
+
63
+ .re-button:hover {
64
+ background-color: var(--re-color-accent-700);
65
+ }
66
+
67
+ .re-button:active {
68
+ background-color: var(--re-color-accent-800);
69
+ }
70
+
71
+ .re-button:focus-visible {
72
+ outline: none;
73
+ box-shadow: var(--re-shadow-focus);
74
+ }
75
+
76
+ /* ---- Disabled (native + aria) ---------------------------------------- */
77
+ .re-button:disabled,
78
+ .re-button[aria-disabled="true"] {
79
+ cursor: not-allowed;
80
+ opacity: 0.55;
81
+ pointer-events: none;
82
+ }
83
+ /* Allow aria-disabled link buttons to keep keyboard focus. */
84
+ a.re-button[aria-disabled="true"] {
85
+ pointer-events: none;
86
+ }
87
+
88
+ /* ---- Variants -------------------------------------------------------- */
89
+ .re-button[data-variant="secondary"] {
90
+ color: var(--re-color-text);
91
+ background-color: var(--re-color-bg);
92
+ border-color: var(--re-color-border-strong);
93
+ }
94
+ .re-button[data-variant="secondary"]:hover {
95
+ background-color: var(--re-color-bg-subtle);
96
+ }
97
+ .re-button[data-variant="secondary"]:active {
98
+ background-color: var(--re-color-bg-muted);
99
+ }
100
+
101
+ .re-button[data-variant="ghost"] {
102
+ color: var(--re-color-text);
103
+ background-color: transparent;
104
+ border-color: transparent;
105
+ }
106
+ .re-button[data-variant="ghost"]:hover {
107
+ background-color: var(--re-color-bg-subtle);
108
+ }
109
+ .re-button[data-variant="ghost"]:active {
110
+ background-color: var(--re-color-bg-muted);
111
+ }
112
+
113
+ .re-button[data-variant="danger"] {
114
+ color: var(--re-color-text-on-accent);
115
+ background-color: var(--re-color-danger-600);
116
+ }
117
+ .re-button[data-variant="danger"]:hover {
118
+ background-color: var(--re-color-danger-700);
119
+ }
120
+ .re-button[data-variant="danger"]:active {
121
+ background-color: var(--re-color-danger-700);
122
+ filter: brightness(0.95);
123
+ }
124
+
125
+ /* ---- Sizes ----------------------------------------------------------- */
126
+ .re-button[data-size="sm"] {
127
+ block-size: var(--re-control-height-sm);
128
+ padding-inline: var(--re-space-3);
129
+ font-size: var(--re-size-text-sm);
130
+ }
131
+ .re-button[data-size="lg"] {
132
+ block-size: var(--re-control-height-lg);
133
+ padding-inline: var(--re-space-5);
134
+ font-size: var(--re-size-text-lg);
135
+ }
136
+
137
+ /* ---- Full-width modifier -------------------------------------------- */
138
+ .re-button[data-full-width] {
139
+ inline-size: 100%;
140
+ }
141
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Relements dialog.
3
+ *
4
+ * Styled native <dialog>. The browser owns showModal/show/close, focus
5
+ * trap, backdrop, and Escape-to-dismiss. This stylesheet supplies
6
+ * surface, header/body/footer slots, and the backdrop look.
7
+ *
8
+ * <dialog class="re-dialog">
9
+ * <header class="re-dialog__header">
10
+ * <h2 class="re-dialog__title">…</h2>
11
+ * <button class="re-dialog__close" aria-label="Close">×</button>
12
+ * </header>
13
+ * <div class="re-dialog__body">…</div>
14
+ * <footer class="re-dialog__footer">…</footer>
15
+ * </dialog>
16
+ */
17
+
18
+ @layer re.components {
19
+ /* Restore the native hide-when-closed behaviour.
20
+ CSS layers beat the UA stylesheet, so display:flex without this
21
+ override makes ALL .re-dialog elements visible at all times. */
22
+ .re-dialog:not([open]) {
23
+ display: none;
24
+ }
25
+
26
+ .re-dialog {
27
+ box-sizing: border-box;
28
+ border: var(--re-border-default);
29
+ border-radius: var(--re-radius-lg);
30
+ background-color: var(--re-color-surface);
31
+ color: var(--re-color-text);
32
+ padding: 0;
33
+ inline-size: min(90vw, 32rem);
34
+ max-block-size: min(85vh, 40rem);
35
+ box-shadow: var(--re-shadow-lg);
36
+ overflow: hidden;
37
+ display: flex;
38
+ flex-direction: column;
39
+ }
40
+
41
+ /* Backdrop owned by the browser; tint with tokens. */
42
+ .re-dialog::backdrop {
43
+ background-color: rgb(0 0 0 / 0.4);
44
+ backdrop-filter: blur(2px);
45
+ }
46
+
47
+ .re-dialog:focus-visible {
48
+ outline: none;
49
+ }
50
+
51
+ .re-dialog__header {
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: space-between;
55
+ gap: var(--re-space-3);
56
+ padding: var(--re-space-4);
57
+ border-block-end: var(--re-border-default);
58
+ }
59
+
60
+ .re-dialog__title {
61
+ margin: 0;
62
+ font-size: var(--re-size-text-lg);
63
+ font-weight: var(--re-font-weight-semibold);
64
+ line-height: var(--re-line-height-tight);
65
+ color: var(--re-color-text);
66
+ }
67
+
68
+ .re-dialog__close {
69
+ appearance: none;
70
+ background: transparent;
71
+ border: 0;
72
+ cursor: pointer;
73
+ padding: var(--re-space-1) var(--re-space-2);
74
+ border-radius: var(--re-radius-md);
75
+ color: var(--re-color-text-muted);
76
+ font: inherit;
77
+ font-size: var(--re-size-text-xl);
78
+ line-height: 1;
79
+ transition: background-color var(--re-duration-fast) var(--re-easing-standard);
80
+ }
81
+
82
+ .re-dialog__close:hover {
83
+ background-color: var(--re-color-bg-subtle);
84
+ color: var(--re-color-text);
85
+ }
86
+
87
+ .re-dialog__close:focus-visible {
88
+ outline: none;
89
+ box-shadow: var(--re-shadow-focus);
90
+ }
91
+
92
+ .re-dialog__body {
93
+ padding: var(--re-space-4);
94
+ overflow-y: auto;
95
+ line-height: var(--re-line-height-normal);
96
+ }
97
+
98
+ .re-dialog__footer {
99
+ display: flex;
100
+ justify-content: flex-end;
101
+ gap: var(--re-space-3);
102
+ padding: var(--re-space-4);
103
+ border-block-start: var(--re-border-default);
104
+ background-color: var(--re-color-bg-subtle);
105
+ }
106
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Relements disclosure.
3
+ *
4
+ * Styled native <details>/<summary>. Toggle behavior, keyboard, and
5
+ * accessibility are owned by the browser; this stylesheet supplies layout
6
+ * and visual affordances only.
7
+ *
8
+ * <details class="re-disclosure">
9
+ * <summary class="re-disclosure__summary">Title</summary>
10
+ * <div class="re-disclosure__body">…</div>
11
+ * </details>
12
+ *
13
+ * Variants (data-variant on <details>):
14
+ * default — bordered card
15
+ * plain — borderless, just a hover background on the summary
16
+ */
17
+
18
+ @layer re.components {
19
+ .re-disclosure {
20
+ border: var(--re-border-default);
21
+ border-radius: var(--re-radius-md);
22
+ background-color: var(--re-color-surface);
23
+ overflow: hidden; /* keep summary corners aligned with the border-radius */
24
+ }
25
+
26
+ .re-disclosure__summary {
27
+ list-style: none; /* hide default marker; replaced with our chevron */
28
+ cursor: pointer;
29
+ padding: var(--re-space-3) var(--re-space-4);
30
+ font-weight: var(--re-font-weight-medium);
31
+ color: var(--re-color-text);
32
+ display: flex;
33
+ align-items: center;
34
+ gap: var(--re-space-3);
35
+ user-select: none;
36
+ transition: background-color var(--re-duration-fast) var(--re-easing-standard);
37
+ }
38
+ .re-disclosure__summary::-webkit-details-marker {
39
+ display: none;
40
+ }
41
+
42
+ .re-disclosure__summary::before {
43
+ content: "";
44
+ flex-shrink: 0;
45
+ inline-size: 0.6rem;
46
+ block-size: 0.6rem;
47
+ border-inline-end: 2px solid currentColor;
48
+ border-block-end: 2px solid currentColor;
49
+ transform: rotate(-45deg);
50
+ transition: transform var(--re-duration-fast) var(--re-easing-standard);
51
+ margin-inline-end: var(--re-space-1);
52
+ }
53
+
54
+ .re-disclosure[open] > .re-disclosure__summary::before {
55
+ transform: rotate(45deg);
56
+ }
57
+
58
+ .re-disclosure__summary:hover {
59
+ background-color: var(--re-color-bg-subtle);
60
+ }
61
+
62
+ .re-disclosure__summary:focus-visible {
63
+ outline: none;
64
+ box-shadow: var(--re-shadow-focus);
65
+ }
66
+
67
+ .re-disclosure__body {
68
+ padding: var(--re-space-3) var(--re-space-4) var(--re-space-4);
69
+ border-block-start: var(--re-border-default);
70
+ color: var(--re-color-text);
71
+ line-height: var(--re-line-height-normal);
72
+ }
73
+
74
+ /* ---- Variants -------------------------------------------------------- */
75
+ .re-disclosure[data-variant="plain"] {
76
+ border: 0;
77
+ background: transparent;
78
+ }
79
+ .re-disclosure[data-variant="plain"] > .re-disclosure__body {
80
+ border-block-start: 0;
81
+ padding-block-start: var(--re-space-2);
82
+ }
83
+ }