@raylang/elements 0.0.0-stage → 0.4.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.
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +12 -2
- package/base.css +111 -0
- package/components/accordion/accordion.js +197 -0
- package/components/alert/alert.js +112 -0
- package/components/app-bar/app-bar.js +168 -0
- package/components/app-shell/app-shell.js +198 -0
- package/components/aspect-ratio/aspect-ratio.js +59 -0
- package/components/attachment/attachment.js +333 -0
- package/components/avatar/avatar.js +96 -0
- package/components/badge/badge.js +63 -0
- package/components/blockquote/blockquote.js +128 -0
- package/components/bottom-nav/bottom-nav.js +309 -0
- package/components/breadcrumbs/breadcrumbs.js +175 -0
- package/components/button/button.js +283 -0
- package/components/button-group/button-group.js +72 -0
- package/components/card/card.js +73 -0
- package/components/carousel/carousel.js +435 -0
- package/components/center/center.js +26 -0
- package/components/checkbox/checkbox.js +152 -0
- package/components/checkbox-group/checkbox-group.js +152 -0
- package/components/code/code.js +574 -0
- package/components/color-picker/color-picker.js +775 -0
- package/components/combobox/combobox.js +563 -0
- package/components/command-palette/command-palette.js +510 -0
- package/components/container/container.js +41 -0
- package/components/date-picker/date-picker.js +594 -0
- package/components/dialog/dialog.js +329 -0
- package/components/drawer/drawer.js +139 -0
- package/components/empty-state/empty-state.js +66 -0
- package/components/file-upload/file-upload.js +589 -0
- package/components/grid/grid.js +96 -0
- package/components/group/group.js +40 -0
- package/components/icon/icon.js +58 -0
- package/components/icon-button/icon-button.js +30 -0
- package/components/indicator/indicator.js +112 -0
- package/components/input/input.js +85 -0
- package/components/kbd/kbd.js +60 -0
- package/components/menu/menu.js +296 -0
- package/components/nav-link/nav-link.js +159 -0
- package/components/notification/notification.js +591 -0
- package/components/number-input/number-input.js +241 -0
- package/components/pagination/pagination.js +194 -0
- package/components/password-input/password-input.js +85 -0
- package/components/path-input/path-input.js +137 -0
- package/components/popover/popover.js +85 -0
- package/components/progress/progress.js +109 -0
- package/components/radio/radio.js +253 -0
- package/components/scroll-area/scroll-area.js +158 -0
- package/components/search/search.js +157 -0
- package/components/segmented-control/segmented-control.js +190 -0
- package/components/select/select.js +506 -0
- package/components/separator/separator.js +58 -0
- package/components/sidebar/sidebar.js +252 -0
- package/components/skeleton/skeleton.js +90 -0
- package/components/slider/slider.js +155 -0
- package/components/spinner/spinner.js +39 -0
- package/components/split/split.js +221 -0
- package/components/stack/stack.js +46 -0
- package/components/stat/stat.js +79 -0
- package/components/statusbar/statusbar.js +110 -0
- package/components/stepper/stepper.js +239 -0
- package/components/switch/switch.js +45 -0
- package/components/table/table.js +328 -0
- package/components/tabs/tabs.js +224 -0
- package/components/textarea/textarea.js +105 -0
- package/components/theme-toggle/theme-toggle.js +78 -0
- package/components/timeline/timeline.js +150 -0
- package/components/toggle/toggle.js +130 -0
- package/components/toggle-group/toggle-group.js +216 -0
- package/components/tooltip/tooltip.js +101 -0
- package/core/charts.js +107 -0
- package/core/color.js +110 -0
- package/core/core.js +556 -0
- package/core/dismiss.js +69 -0
- package/core/floating.js +195 -0
- package/core/focus.js +70 -0
- package/core/form.js +235 -0
- package/core/host.js +200 -0
- package/core/hotkey.js +40 -0
- package/core/icons.js +104 -0
- package/core/motion.js +73 -0
- package/core/position.js +91 -0
- package/core/raylang.js +167 -0
- package/core/scroll.js +58 -0
- package/core/styles.js +211 -0
- package/core.js +16 -0
- package/custom-elements.json +18545 -0
- package/index.js +74 -0
- package/package.json +312 -4
- package/ray-elements.d.ts +2570 -0
- package/tokens.css +796 -0
- package/tokens.json +2083 -0
- package/vscode.html-custom-data.json +5499 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// <ray-button>: a button (or a link, with href) in Mantine's variants.
|
|
2
|
+
|
|
3
|
+
import { RayElement, css, define } from "../../core/core.js";
|
|
4
|
+
import { applyColor, applyGradient, sizeStyles, radiusStyles, variantStyles, loaderStyles } from "../../core/styles.js";
|
|
5
|
+
|
|
6
|
+
const styles = css`
|
|
7
|
+
:host {
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
vertical-align: middle;
|
|
10
|
+
}
|
|
11
|
+
:host([full-width]) { display: flex; width: 100%; }
|
|
12
|
+
.button {
|
|
13
|
+
position: relative;
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
flex: 1;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
gap: var(--_gap);
|
|
19
|
+
height: var(--_h);
|
|
20
|
+
padding: 0 var(--_px);
|
|
21
|
+
font: inherit;
|
|
22
|
+
font-size: var(--_fs);
|
|
23
|
+
font-weight: var(--ray-font-weight-semibold);
|
|
24
|
+
line-height: 1;
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
text-decoration: none;
|
|
27
|
+
border-radius: var(--_r);
|
|
28
|
+
/* Corners a ray-button-group can square off (inherited custom properties). */
|
|
29
|
+
border-start-start-radius: var(--ray-group-radius-ss, var(--_r));
|
|
30
|
+
border-start-end-radius: var(--ray-group-radius-se, var(--_r));
|
|
31
|
+
border-end-start-radius: var(--ray-group-radius-es, var(--_r));
|
|
32
|
+
border-end-end-radius: var(--ray-group-radius-ee, var(--_r));
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
user-select: none;
|
|
35
|
+
-webkit-tap-highlight-color: transparent;
|
|
36
|
+
}
|
|
37
|
+
.button:not([data-disabled]):active { transform: translateY(1px); }
|
|
38
|
+
|
|
39
|
+
/* variant="raised": a physical key. The face sits on a darker edge (a hard
|
|
40
|
+
shadow, like ray-kbd's thick bottom border); pressing sinks the face onto
|
|
41
|
+
it. The host reserves the edge's height, so the row does not jump. */
|
|
42
|
+
:host([variant="raised"]) { --_depth: 4px; padding-bottom: var(--_depth); }
|
|
43
|
+
:host([variant="raised"][size="xs"]), :host([variant="raised"][size="sm"]) { --_depth: 3px; }
|
|
44
|
+
.button[data-variant="raised"] {
|
|
45
|
+
/* Face, its text and the edge: overridable (ray-toggle's unlit key). */
|
|
46
|
+
--_face: var(--_filled);
|
|
47
|
+
--_face-text: var(--_contrast);
|
|
48
|
+
--_edge: color-mix(in srgb, var(--_face), black 32%);
|
|
49
|
+
--_rim: color-mix(in srgb, var(--_face), black 14%);
|
|
50
|
+
background: var(--_face);
|
|
51
|
+
color: var(--_face-text);
|
|
52
|
+
border-color: var(--_rim);
|
|
53
|
+
box-shadow:
|
|
54
|
+
0 var(--_depth) 0 var(--_edge),
|
|
55
|
+
0 calc(var(--_depth) + 1px) 6px -1px rgb(0 0 0 / 0.28),
|
|
56
|
+
inset 0 1px 0 rgb(255 255 255 / 0.18);
|
|
57
|
+
transition:
|
|
58
|
+
transform 110ms var(--ray-easing-out),
|
|
59
|
+
box-shadow 110ms var(--ray-easing-out),
|
|
60
|
+
background-color var(--ray-duration-fast) var(--ray-easing-standard);
|
|
61
|
+
}
|
|
62
|
+
.button[data-variant="raised"][data-interactive]:not([data-disabled]):hover {
|
|
63
|
+
background: color-mix(in srgb, var(--_face), white 6%);
|
|
64
|
+
}
|
|
65
|
+
.button[data-variant="raised"]:not([data-disabled]):is(:active, [data-pressed]) {
|
|
66
|
+
transform: translateY(var(--_depth));
|
|
67
|
+
box-shadow: 0 0 0 var(--_edge), 0 1px 2px -1px rgb(0 0 0 / 0.3), inset 0 1px 2px rgb(0 0 0 / 0.12);
|
|
68
|
+
transition-duration: 50ms;
|
|
69
|
+
}
|
|
70
|
+
/* Disabled or loading: pushed in and greyed. */
|
|
71
|
+
.button[data-variant="raised"][data-disabled] {
|
|
72
|
+
transform: translateY(var(--_depth));
|
|
73
|
+
color: var(--ray-muted-foreground);
|
|
74
|
+
background: var(--ray-muted);
|
|
75
|
+
border-color: var(--ray-border);
|
|
76
|
+
box-shadow: none;
|
|
77
|
+
}
|
|
78
|
+
.section { display: inline-flex; align-items: center; }
|
|
79
|
+
.section:not([data-filled]) { display: none; }
|
|
80
|
+
.label { display: inline-flex; align-items: center; overflow: hidden; text-overflow: ellipsis; }
|
|
81
|
+
.overlay {
|
|
82
|
+
position: absolute;
|
|
83
|
+
inset: 0;
|
|
84
|
+
display: none;
|
|
85
|
+
align-items: center;
|
|
86
|
+
justify-content: center;
|
|
87
|
+
--_loader: calc(var(--_fs) * 1.15);
|
|
88
|
+
}
|
|
89
|
+
:host([loading]) .overlay { display: flex; }
|
|
90
|
+
/* Hidden by opacity, not visibility: the label stays the button's
|
|
91
|
+
accessible name while the spinner shows ("Saving", busy). */
|
|
92
|
+
:host([loading]) .content { opacity: 0; }
|
|
93
|
+
.content { display: contents; }
|
|
94
|
+
:host([loading]) .button[data-disabled] { cursor: progress; }
|
|
95
|
+
:host([loading]) .button[data-variant="filled"][data-disabled] {
|
|
96
|
+
background: var(--_filled);
|
|
97
|
+
color: var(--_contrast);
|
|
98
|
+
border-color: transparent;
|
|
99
|
+
opacity: 0.75;
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
const INNER = `
|
|
104
|
+
<span class="content">
|
|
105
|
+
<span class="section" part="left-section"><slot name="left"></slot></span>
|
|
106
|
+
<span class="label" part="label"><slot></slot></span>
|
|
107
|
+
<span class="section" part="right-section"><slot name="right"></slot></span>
|
|
108
|
+
</span>
|
|
109
|
+
<span class="overlay" aria-hidden="true"><span class="loader"></span></span>`;
|
|
110
|
+
|
|
111
|
+
const PRESS_MS = 110;
|
|
112
|
+
|
|
113
|
+
// Host attributes copied to the inner <button>/<a>.
|
|
114
|
+
const MIRRORED_ARIA = ["aria-label", "aria-expanded", "aria-haspopup", "aria-pressed", "aria-current"];
|
|
115
|
+
|
|
116
|
+
export class RayButton extends RayElement {
|
|
117
|
+
static formAssociated = true;
|
|
118
|
+
static delegatesFocus = true;
|
|
119
|
+
static properties = {
|
|
120
|
+
variant: { type: String, reflect: true, default: "filled" },
|
|
121
|
+
color: { type: String, reflect: true },
|
|
122
|
+
gradient: { type: String, reflect: true },
|
|
123
|
+
size: { type: String, reflect: true, default: "sm" },
|
|
124
|
+
radius: { type: String, reflect: true },
|
|
125
|
+
type: { type: String, reflect: true, default: "button" },
|
|
126
|
+
disabled: { type: Boolean, reflect: true },
|
|
127
|
+
loading: { type: Boolean, reflect: true },
|
|
128
|
+
fullWidth: { type: Boolean, reflect: true },
|
|
129
|
+
href: { type: String, reflect: true },
|
|
130
|
+
target: { type: String, reflect: true },
|
|
131
|
+
name: { type: String, reflect: true },
|
|
132
|
+
value: { type: String, reflect: true },
|
|
133
|
+
};
|
|
134
|
+
static styles = [sizeStyles, radiusStyles, variantStyles, loaderStyles, styles];
|
|
135
|
+
|
|
136
|
+
static get observedAttributes() {
|
|
137
|
+
return [...super.observedAttributes, ...MIRRORED_ARIA, "aria-describedby"];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
attributeChangedCallback(attr, old, value) {
|
|
141
|
+
if (attr.startsWith("aria-")) {
|
|
142
|
+
this.requestUpdate();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
super.attributeChangedCallback(attr, old, value);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
constructor() {
|
|
149
|
+
super();
|
|
150
|
+
this.internals = this.attachInternals();
|
|
151
|
+
this._onClick = (e) => this._click(e);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
get form() { return this.internals.form; }
|
|
155
|
+
|
|
156
|
+
formDisabledCallback(disabled) {
|
|
157
|
+
this._fieldsetDisabled = disabled;
|
|
158
|
+
this.requestUpdate();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Clicks the inner button (host.click() alone would not reach it). */
|
|
162
|
+
click() {
|
|
163
|
+
if (this._el) this._el.click();
|
|
164
|
+
else super.click();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
update() {
|
|
168
|
+
// The inner element is a <button>, or an <a> when href is set.
|
|
169
|
+
const tag = this.href ? "a" : "button";
|
|
170
|
+
if (!this._el || this._el.localName !== tag) this._render(tag);
|
|
171
|
+
const el = this._el;
|
|
172
|
+
const disabled = Boolean(this.disabled || this.loading || this._fieldsetDisabled);
|
|
173
|
+
|
|
174
|
+
el.dataset.variant = this.variant || "filled";
|
|
175
|
+
el.toggleAttribute("data-disabled", disabled);
|
|
176
|
+
applyColor(el, this.color);
|
|
177
|
+
applyGradient(el, this.gradient);
|
|
178
|
+
if (tag === "button") {
|
|
179
|
+
el.disabled = Boolean(this.disabled || this._fieldsetDisabled);
|
|
180
|
+
el.setAttribute("aria-disabled", String(disabled));
|
|
181
|
+
} else {
|
|
182
|
+
el.href = this.href;
|
|
183
|
+
if (this.target) el.target = this.target;
|
|
184
|
+
else el.removeAttribute("target");
|
|
185
|
+
if (disabled) {
|
|
186
|
+
el.setAttribute("aria-disabled", "true");
|
|
187
|
+
el.removeAttribute("href");
|
|
188
|
+
} else {
|
|
189
|
+
el.removeAttribute("aria-disabled");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
el.toggleAttribute("aria-busy", Boolean(this.loading));
|
|
193
|
+
// ARIA put on the host (by the app, a popover, a menu…) belongs to the inner button.
|
|
194
|
+
for (const attr of MIRRORED_ARIA) {
|
|
195
|
+
const v = this.getAttribute(attr);
|
|
196
|
+
if (v !== null) el.setAttribute(attr, v);
|
|
197
|
+
else el.removeAttribute(attr);
|
|
198
|
+
}
|
|
199
|
+
// aria-describedby ids live in the document: reference the elements
|
|
200
|
+
// themselves (element reflection may point out of the shadow root).
|
|
201
|
+
const ids = (this.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean);
|
|
202
|
+
if ("ariaDescribedByElements" in el) {
|
|
203
|
+
el.ariaDescribedByElements = ids.length ? ids.map((id) => this.getRootNode().getElementById?.(id) ?? document.getElementById(id)).filter(Boolean) : null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_render(tag) {
|
|
208
|
+
this._el?.remove();
|
|
209
|
+
const el = document.createElement(tag);
|
|
210
|
+
el.className = "button focus-ring";
|
|
211
|
+
el.setAttribute("part", "button");
|
|
212
|
+
el.setAttribute("data-interactive", "");
|
|
213
|
+
if (tag === "button") el.type = "button";
|
|
214
|
+
el.innerHTML = INNER;
|
|
215
|
+
el.addEventListener("click", this._onClick);
|
|
216
|
+
this._wirePress(el);
|
|
217
|
+
this.shadowRoot.append(el);
|
|
218
|
+
this._el = el;
|
|
219
|
+
for (const slot of el.querySelectorAll("slot[name]")) {
|
|
220
|
+
const sync = () => slot.parentElement.toggleAttribute("data-filled", slot.assignedNodes().length > 0);
|
|
221
|
+
slot.addEventListener("slotchange", sync);
|
|
222
|
+
sync();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// The raised variant's press, for touch (iOS applies :active only with a
|
|
227
|
+
// touch listener) and keyboard (Enter never sets :active). A press shows
|
|
228
|
+
// for at least PRESS_MS, so a quick tap still visibly sinks the key.
|
|
229
|
+
_wirePress(el) {
|
|
230
|
+
let since = 0;
|
|
231
|
+
let timer = 0;
|
|
232
|
+
const down = () => {
|
|
233
|
+
if (this.variant !== "raised" || el.hasAttribute("data-disabled")) return;
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
since = performance.now();
|
|
236
|
+
el.setAttribute("data-pressed", "");
|
|
237
|
+
};
|
|
238
|
+
const up = () => {
|
|
239
|
+
if (!el.hasAttribute("data-pressed")) return;
|
|
240
|
+
clearTimeout(timer);
|
|
241
|
+
timer = setTimeout(() => el.removeAttribute("data-pressed"), Math.max(0, PRESS_MS - (performance.now() - since)));
|
|
242
|
+
};
|
|
243
|
+
el.addEventListener("pointerdown", down);
|
|
244
|
+
for (const t of ["pointerup", "pointercancel", "pointerleave", "blur"]) el.addEventListener(t, up);
|
|
245
|
+
el.addEventListener("keydown", (e) => {
|
|
246
|
+
if ((e.key === "Enter" || e.key === " ") && !e.repeat) down();
|
|
247
|
+
});
|
|
248
|
+
el.addEventListener("keyup", (e) => {
|
|
249
|
+
if (e.key === "Enter" || e.key === " ") up();
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_click(e) {
|
|
254
|
+
if (this.loading || this.disabled) {
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
e.stopImmediatePropagation();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const form = this.internals.form;
|
|
260
|
+
if (!form || this.href) return;
|
|
261
|
+
if (this.type === "submit") {
|
|
262
|
+
// Submit with this button as the submitter-equivalent: name/value travel
|
|
263
|
+
// as a hidden field for the duration of the submit.
|
|
264
|
+
let extra = null;
|
|
265
|
+
if (this.name) {
|
|
266
|
+
extra = document.createElement("input");
|
|
267
|
+
extra.type = "hidden";
|
|
268
|
+
extra.name = this.name;
|
|
269
|
+
extra.value = this.value ?? "";
|
|
270
|
+
form.append(extra);
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
form.requestSubmit();
|
|
274
|
+
} finally {
|
|
275
|
+
extra?.remove();
|
|
276
|
+
}
|
|
277
|
+
} else if (this.type === "reset") {
|
|
278
|
+
form.reset();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
define("ray-button", RayButton);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// <ray-button-group>: buttons (and fields, menus…) joined into one control
|
|
2
|
+
// (Mantine's Button.Group, shadcn's ButtonGroup). The group squares off the
|
|
3
|
+
// inner corners of its children through inherited custom properties
|
|
4
|
+
// (--ray-group-radius-*, read by ray-button, ray-icon-button and the field
|
|
5
|
+
// boxes) and overlaps their borders.
|
|
6
|
+
|
|
7
|
+
import { RayElement, css, define } from "../../core/core.js";
|
|
8
|
+
|
|
9
|
+
const styles = css`
|
|
10
|
+
:host { display: inline-flex; vertical-align: middle; isolation: isolate; }
|
|
11
|
+
:host([orientation="vertical"]) { flex-direction: column; }
|
|
12
|
+
:host([full-width]) { display: flex; }
|
|
13
|
+
:host([full-width]) ::slotted(*) { flex: 1; }
|
|
14
|
+
::slotted(*) { position: relative; }
|
|
15
|
+
/* The hovered or focused child on top: its whole border shows. */
|
|
16
|
+
::slotted(:hover) { z-index: 1; }
|
|
17
|
+
/* A pressed toggle's colored border must not hide under its neighbors,
|
|
18
|
+
nor flicker as the pointer moves over them. */
|
|
19
|
+
::slotted([pressed]) { z-index: 2; }
|
|
20
|
+
::slotted(:focus-within), ::slotted(:focus) { z-index: 3; }
|
|
21
|
+
|
|
22
|
+
/* Horizontal: square the inside corners, overlap the 1px borders. */
|
|
23
|
+
:host(:not([orientation="vertical"])) ::slotted(:not(:first-child)) {
|
|
24
|
+
margin-inline-start: -1px;
|
|
25
|
+
--ray-group-radius-ss: 0px;
|
|
26
|
+
--ray-group-radius-es: 0px;
|
|
27
|
+
}
|
|
28
|
+
:host(:not([orientation="vertical"])) ::slotted(:not(:last-child)) {
|
|
29
|
+
--ray-group-radius-se: 0px;
|
|
30
|
+
--ray-group-radius-ee: 0px;
|
|
31
|
+
}
|
|
32
|
+
/* Vertical: the same on the block axis. */
|
|
33
|
+
:host([orientation="vertical"]) ::slotted(:not(:first-child)) {
|
|
34
|
+
margin-block-start: -1px;
|
|
35
|
+
--ray-group-radius-ss: 0px;
|
|
36
|
+
--ray-group-radius-se: 0px;
|
|
37
|
+
}
|
|
38
|
+
:host([orientation="vertical"]) ::slotted(:not(:last-child)) {
|
|
39
|
+
--ray-group-radius-es: 0px;
|
|
40
|
+
--ray-group-radius-ee: 0px;
|
|
41
|
+
}
|
|
42
|
+
/* Filled buttons have no border to overlap: a thin divider instead. */
|
|
43
|
+
:host(:not([orientation="vertical"])) ::slotted(ray-button[variant="filled"]:not(:first-child)),
|
|
44
|
+
:host(:not([orientation="vertical"])) ::slotted(ray-button:not([variant]):not(:first-child)),
|
|
45
|
+
:host(:not([orientation="vertical"])) ::slotted(ray-menu:not(:first-child)) {
|
|
46
|
+
box-shadow: -1px 0 0 0 color-mix(in srgb, var(--ray-background) 35%, transparent);
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
export class RayButtonGroup extends RayElement {
|
|
51
|
+
static properties = {
|
|
52
|
+
orientation: { type: String, reflect: true, default: "horizontal" },
|
|
53
|
+
fullWidth: { type: Boolean, reflect: true },
|
|
54
|
+
label: { type: String },
|
|
55
|
+
};
|
|
56
|
+
static styles = [styles];
|
|
57
|
+
static template = `<slot></slot>`;
|
|
58
|
+
|
|
59
|
+
connectedCallback() {
|
|
60
|
+
super.connectedCallback();
|
|
61
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "group");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
update() {
|
|
65
|
+
if (this.label) this.setAttribute("aria-label", this.label);
|
|
66
|
+
// role=group takes no aria-orientation (ARIA allows it on toolbars,
|
|
67
|
+
// sliders, separators…): the orientation shows, it is not announced.
|
|
68
|
+
this.removeAttribute("aria-orientation");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
define("ray-button-group", RayButtonGroup);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// <ray-card>: a surface with optional header and footer.
|
|
2
|
+
|
|
3
|
+
import { RayElement, css, define } from "../../core/core.js";
|
|
4
|
+
import { radiusStyles } from "../../core/styles.js";
|
|
5
|
+
|
|
6
|
+
const styles = css`
|
|
7
|
+
:host {
|
|
8
|
+
display: block;
|
|
9
|
+
--_p: var(--ray-space-md);
|
|
10
|
+
--_shadow: none;
|
|
11
|
+
}
|
|
12
|
+
:host([padding="xs"]) { --_p: var(--ray-space-xs); }
|
|
13
|
+
:host([padding="sm"]) { --_p: var(--ray-space-sm); }
|
|
14
|
+
:host([padding="lg"]) { --_p: var(--ray-space-lg); }
|
|
15
|
+
:host([padding="xl"]) { --_p: var(--ray-space-xl); }
|
|
16
|
+
:host([padding="0"]) { --_p: 0; }
|
|
17
|
+
:host([shadow="xs"]) { --_shadow: var(--ray-shadow-xs); }
|
|
18
|
+
:host([shadow="sm"]) { --_shadow: var(--ray-shadow-sm); }
|
|
19
|
+
:host([shadow="md"]) { --_shadow: var(--ray-shadow-md); }
|
|
20
|
+
:host([shadow="lg"]) { --_shadow: var(--ray-shadow-lg); }
|
|
21
|
+
:host([shadow="xl"]) { --_shadow: var(--ray-shadow-xl); }
|
|
22
|
+
.card {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
height: 100%;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
color: var(--ray-card-foreground);
|
|
28
|
+
background: var(--ray-card);
|
|
29
|
+
border: 1px solid transparent;
|
|
30
|
+
border-radius: var(--_r);
|
|
31
|
+
box-shadow: var(--_shadow);
|
|
32
|
+
}
|
|
33
|
+
:host([with-border]) .card,
|
|
34
|
+
:host([variant="outline"]) .card { border-color: var(--ray-border); }
|
|
35
|
+
:host([variant="filled"]) .card { background: var(--ray-muted); }
|
|
36
|
+
:host([variant="outline"]) .card { background: transparent; }
|
|
37
|
+
.header, .footer { padding: var(--_p); }
|
|
38
|
+
.header { padding-bottom: 0; }
|
|
39
|
+
.footer { padding-top: 0; }
|
|
40
|
+
.header:not([data-filled]), .footer:not([data-filled]) { display: none; }
|
|
41
|
+
.body { flex: 1; padding: var(--_p); }
|
|
42
|
+
::slotted([slot="media"]) { display: block; width: 100%; }
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
export class RayCard extends RayElement {
|
|
46
|
+
static properties = {
|
|
47
|
+
padding: { type: String, reflect: true },
|
|
48
|
+
radius: { type: String, reflect: true },
|
|
49
|
+
shadow: { type: String, reflect: true },
|
|
50
|
+
withBorder: { type: Boolean, reflect: true },
|
|
51
|
+
variant: { type: String, reflect: true, default: "default" },
|
|
52
|
+
};
|
|
53
|
+
static styles = [radiusStyles, styles];
|
|
54
|
+
static template = `
|
|
55
|
+
<div class="card" part="card">
|
|
56
|
+
<slot name="media"></slot>
|
|
57
|
+
<div class="header" part="header"><slot name="header"></slot></div>
|
|
58
|
+
<div class="body" part="body"><slot></slot></div>
|
|
59
|
+
<div class="footer" part="footer"><slot name="footer"></slot></div>
|
|
60
|
+
</div>`;
|
|
61
|
+
|
|
62
|
+
connectedCallback() {
|
|
63
|
+
super.connectedCallback();
|
|
64
|
+
for (const name of ["header", "footer"]) {
|
|
65
|
+
const slot = this.$(`slot[name="${name}"]`);
|
|
66
|
+
const sync = () => slot.parentElement.toggleAttribute("data-filled", slot.assignedNodes().length > 0);
|
|
67
|
+
slot.addEventListener("slotchange", sync);
|
|
68
|
+
sync();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
define("ray-card", RayCard);
|