@birdycom/omniaui 0.12.5 → 0.12.6
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/README.md +1 -1
- package/bin/lib/copy-js.mjs +10 -1
- package/bin/omniaui.mjs +2 -2
- package/dist/barcode.d.ts +15 -0
- package/dist/barcode.js +164 -0
- package/dist/components-extra.css +15 -1
- package/dist/components.css +1489 -187
- package/dist/components.min.css +1 -1
- package/dist/data-table.js +135 -98
- package/dist/form-extras.d.ts +11 -0
- package/dist/form-extras.js +214 -0
- package/dist/lazy.d.ts +5 -0
- package/dist/lazy.js +37 -0
- package/dist/oc-theme.css +1 -1
- package/dist/parts/app-data-table-footer.css +38 -0
- package/dist/parts/app-data-table-rich.css +161 -0
- package/dist/parts/app-data-table.css +44 -74
- package/dist/parts/app-name-card.css +65 -0
- package/dist/parts/app-titlebar.css +16 -8
- package/dist/parts/data-meter-row.css +40 -0
- package/dist/parts/data-price.css +20 -0
- package/dist/parts/data-ribbon.css +76 -0
- package/dist/parts/data-spec-list.css +36 -0
- package/dist/parts/data-stock.css +41 -0
- package/dist/parts/forms-affix-input.css +1 -1
- package/dist/parts/forms-avatar-picker.css +43 -0
- package/dist/parts/forms-date-field.css +53 -0
- package/dist/parts/forms-density.css +13 -0
- package/dist/parts/forms-form-section.css +64 -0
- package/dist/parts/forms-quantity.css +55 -0
- package/dist/parts/forms-rating.css +81 -0
- package/dist/parts/media-attachment.css +1 -1
- package/dist/parts/media-barcode.css +23 -0
- package/dist/parts/media-chat-composer.css +3 -0
- package/dist/parts/media-emoji-picker.css +3 -1
- package/dist/parts/media-gallery.css +55 -0
- package/dist/parts/media-glass-liquid.css +99 -0
- package/dist/parts/media-glass-options.css +50 -0
- package/dist/parts/media-lazy.css +46 -0
- package/dist/parts/media-message-thread.css +1 -1
- package/dist/parts/media-message.css +1 -1
- package/dist/parts/nav-topbar-variants.css +96 -0
- package/dist/parts/site-header-variants.css +70 -0
- package/dist/parts/site-prose.css +1 -1
- package/dist/showcase.css +33 -3
- package/manifest.json +121 -9
- package/package.json +5 -2
- package/snippets/accordion-accent.html +1 -1
- package/snippets/attachment.html +4 -4
- package/snippets/avatar-picker.html +40 -0
- package/snippets/barcode.html +20 -0
- package/snippets/chat-composer.html +3 -3
- package/snippets/data-table.html +51 -76
- package/snippets/date-field.html +44 -0
- package/snippets/doc-viewer.html +1 -1
- package/snippets/emoji-picker.html +1 -1
- package/snippets/form-density.html +25 -0
- package/snippets/form-section.html +47 -0
- package/snippets/gallery.html +25 -0
- package/snippets/lazy.html +39 -0
- package/snippets/message-thread.html +12 -12
- package/snippets/message.html +10 -10
- package/snippets/meter-row.html +29 -0
- package/snippets/name-card.html +72 -0
- package/snippets/page-header.html +1 -1
- package/snippets/price.html +25 -0
- package/snippets/prose.html +3 -3
- package/snippets/quantity.html +12 -0
- package/snippets/rating.html +26 -0
- package/snippets/ribbon.html +48 -0
- package/snippets/sidebar.html +3 -3
- package/snippets/spec-list.html +34 -0
- package/snippets/stock.html +20 -0
- package/snippets/table-actions.html +5 -5
- package/snippets/table.html +2 -2
- package/snippets/titlebar.html +1 -1
- package/snippets/user.html +3 -3
- package/dist/parts/app-column-menu.css +0 -85
- package/snippets/column-menu.html +0 -38
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/data-stock.css | Map: indicator (dot + text) -> levels (ok, low, out, order) -> badge variant -> count */
|
|
2
|
+
/* Stock status: a colored dot with a short text ("In stock", "Only 3 left", "Out of stock", "On order").
|
|
3
|
+
data-level picks the color; the text always says it too, so color is never the only signal. */
|
|
4
|
+
|
|
5
|
+
.oc-stock {
|
|
6
|
+
--oc-stock-color: var(--oc-success);
|
|
7
|
+
/* Inline text (not flex), so "Only <b>3</b> left" keeps normal word spacing. */
|
|
8
|
+
display: inline-block;
|
|
9
|
+
font-size: 0.8125rem;
|
|
10
|
+
font-weight: 500;
|
|
11
|
+
line-height: 1.3;
|
|
12
|
+
color: var(--oc-foreground);
|
|
13
|
+
white-space: nowrap;
|
|
14
|
+
}
|
|
15
|
+
.oc-stock::before {
|
|
16
|
+
content: "";
|
|
17
|
+
display: inline-block;
|
|
18
|
+
margin-inline-end: 0.4rem;
|
|
19
|
+
vertical-align: 0.05em;
|
|
20
|
+
inline-size: 0.5rem;
|
|
21
|
+
block-size: 0.5rem;
|
|
22
|
+
background: var(--oc-stock-color);
|
|
23
|
+
border-radius: 50%;
|
|
24
|
+
box-shadow: 0 0 0 3px oklch(from var(--oc-stock-color) l c h / 0.18);
|
|
25
|
+
}
|
|
26
|
+
.oc-stock[data-level="low"] { --oc-stock-color: var(--oc-warning); }
|
|
27
|
+
.oc-stock[data-level="out"] { --oc-stock-color: var(--oc-destructive); }
|
|
28
|
+
.oc-stock[data-level="order"] { --oc-stock-color: var(--oc-info, light-dark(var(--color-deepskyblue-70), var(--color-skyblue-50))); }
|
|
29
|
+
.oc-stock__count { font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
30
|
+
.oc-stock__note { font-weight: 400; color: var(--oc-muted-foreground); }
|
|
31
|
+
|
|
32
|
+
/* Badge: a tinted pill (table cells, product cards). */
|
|
33
|
+
.oc-stock--badge {
|
|
34
|
+
padding: 0.125rem 0.5rem 0.125rem 0.45rem;
|
|
35
|
+
font-size: 0.75rem;
|
|
36
|
+
color: color-mix(in oklch, var(--oc-stock-color) 75%, var(--oc-foreground));
|
|
37
|
+
background: oklch(from var(--oc-stock-color) l c h / 0.12);
|
|
38
|
+
border: 1px solid oklch(from var(--oc-stock-color) l c h / 0.3);
|
|
39
|
+
border-radius: 999px;
|
|
40
|
+
}
|
|
41
|
+
.oc-stock--badge::before { inline-size: 0.4rem; block-size: 0.4rem; margin-inline-end: 0.3rem; vertical-align: 0.1em; box-shadow: none; }
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
min-inline-size: 0;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/* Symbol column: same chrome as the
|
|
28
|
+
/* Symbol column: the same chrome as the other fields (€, %, @, barcode icon).
|
|
29
29
|
A divider separates the symbol from the value. */
|
|
30
30
|
.oc-input-group.oc-input-group--affix .oc-input-addon {
|
|
31
31
|
align-self: stretch;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-avatar-picker.css | Map: picker -> preview (initials or image) -> file input -> text and buttons -> drop state -> square and logo */
|
|
2
|
+
/* Logo or avatar picker: a preview, "Upload" and "Remove", a hint, and dropping a file
|
|
3
|
+
on the preview. The file input is a real <input type="file">; packages/ui/src/avatar-picker.js shows the preview. */
|
|
4
|
+
|
|
5
|
+
.oc-avatar-picker { display: flex; align-items: center; gap: 1rem; }
|
|
6
|
+
.oc-avatar-picker__preview {
|
|
7
|
+
position: relative;
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
flex-shrink: 0;
|
|
12
|
+
inline-size: var(--oc-avatar-picker-size, 4.5rem);
|
|
13
|
+
block-size: var(--oc-avatar-picker-size, 4.5rem);
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
font-size: 1.25rem;
|
|
16
|
+
font-weight: 600;
|
|
17
|
+
color: var(--oc-muted-foreground);
|
|
18
|
+
background: var(--oc-muted);
|
|
19
|
+
border: 1px dashed var(--oc-border-strong, var(--oc-border));
|
|
20
|
+
border-radius: 50%;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
.oc-avatar-picker--square .oc-avatar-picker__preview { border-radius: var(--oc-radius); }
|
|
24
|
+
.oc-avatar-picker__preview img { inline-size: 100%; block-size: 100%; object-fit: cover; }
|
|
25
|
+
.oc-avatar-picker--logo .oc-avatar-picker__preview img { object-fit: contain; padding: 0.375rem; background: var(--color-white-50); }
|
|
26
|
+
.oc-avatar-picker__preview:has(img) { border-style: solid; }
|
|
27
|
+
.oc-avatar-picker__preview:hover,
|
|
28
|
+
.oc-avatar-picker.is-dragover .oc-avatar-picker__preview {
|
|
29
|
+
color: var(--oc-primary);
|
|
30
|
+
background: oklch(from var(--oc-primary) l c h / 0.08);
|
|
31
|
+
border-color: var(--oc-primary);
|
|
32
|
+
}
|
|
33
|
+
.oc-avatar-picker__preview:has(:focus-visible) { outline: 2px solid var(--oc-ring); outline-offset: 2px; }
|
|
34
|
+
.oc-avatar-picker__file { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
|
|
35
|
+
.oc-avatar-picker__text { display: grid; gap: 0.375rem; min-inline-size: 0; }
|
|
36
|
+
.oc-avatar-picker__label { font-size: 0.875rem; font-weight: 600; }
|
|
37
|
+
.oc-avatar-picker__hint { font-size: 0.75rem; color: var(--oc-muted-foreground); }
|
|
38
|
+
.oc-avatar-picker__actions { display: flex; flex-wrap: wrap; gap: 0.375rem; }
|
|
39
|
+
/* "Remove" only while there is an image. */
|
|
40
|
+
.oc-avatar-picker:not(:has(.oc-avatar-picker__preview img)) [data-oc-avatar-remove] { display: none; }
|
|
41
|
+
/* A refused file (wrong type or too large): red outline and the reason under the buttons. */
|
|
42
|
+
.oc-avatar-picker.is-invalid .oc-avatar-picker__preview { border-color: var(--oc-destructive); }
|
|
43
|
+
.oc-avatar-picker__error { font-size: 0.75rem; font-weight: 500; color: var(--oc-destructive); }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-date-field.css | Map: field frame -> icon zone -> native date input -> ISO week label -> sizes and states */
|
|
2
|
+
/* Date field: calendar icon | line | date, with the ISO week at the end when wanted.
|
|
3
|
+
The date is a native <input type="date"> (keyboard, picker and locale from the browser). The week label is filled
|
|
4
|
+
by packages/ui/src/date-field.js (or by your own code). */
|
|
5
|
+
|
|
6
|
+
.oc-date-field {
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: stretch;
|
|
9
|
+
block-size: 2.5rem;
|
|
10
|
+
background: var(--oc-background);
|
|
11
|
+
border: 1px solid var(--oc-input, var(--oc-border));
|
|
12
|
+
border-radius: var(--oc-radius);
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
}
|
|
15
|
+
.oc-date-field:focus-within { border-color: var(--oc-ring); box-shadow: 0 0 0 3px oklch(from var(--oc-ring) l c h / 0.18); }
|
|
16
|
+
.oc-date-field__icon {
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
inline-size: 2.5rem;
|
|
21
|
+
font-size: 1.125rem;
|
|
22
|
+
color: var(--oc-muted-foreground);
|
|
23
|
+
border-inline-end: 1px solid var(--oc-border);
|
|
24
|
+
}
|
|
25
|
+
.oc-date-field__input {
|
|
26
|
+
min-inline-size: 0;
|
|
27
|
+
padding: 0 0.75rem;
|
|
28
|
+
font: inherit;
|
|
29
|
+
font-size: 0.875rem;
|
|
30
|
+
color: var(--oc-foreground);
|
|
31
|
+
background: transparent;
|
|
32
|
+
border: 0;
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
35
|
+
.oc-date-field__input::-webkit-calendar-picker-indicator { opacity: 0.55; cursor: pointer; }
|
|
36
|
+
/* ISO week: a quiet label at the end ("Week 45"); hidden while there is no date. */
|
|
37
|
+
.oc-date-field__week {
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
padding-inline: 0.75rem;
|
|
41
|
+
font-size: 0.75rem;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
font-variant-numeric: tabular-nums;
|
|
44
|
+
color: var(--oc-primary);
|
|
45
|
+
background: oklch(from var(--oc-primary) l c h / 0.08);
|
|
46
|
+
border-inline-start: 1px solid var(--oc-border);
|
|
47
|
+
white-space: nowrap;
|
|
48
|
+
}
|
|
49
|
+
.oc-date-field__week:empty { display: none; }
|
|
50
|
+
.oc-date-field--sm { block-size: 2rem; }
|
|
51
|
+
.oc-date-field--sm .oc-date-field__icon { inline-size: 2rem; font-size: 1rem; }
|
|
52
|
+
.oc-date-field:has(.oc-date-field__input:disabled) { opacity: 0.55; }
|
|
53
|
+
.oc-date-field:has(.oc-date-field__input[aria-invalid="true"]) { border-color: var(--oc-destructive); }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-density.css | Map: compact scope -> inputs, selects, date fields, textareas -> buttons -> field gaps */
|
|
2
|
+
/* Form density: .oc-density-compact on a form or any wrapper makes its controls
|
|
3
|
+
2rem high instead of 2.5rem, for dense editors (invoice templates, inline tables). The default needs no class. */
|
|
4
|
+
|
|
5
|
+
.oc-density-compact :is(.oc-input, .oc-select, .oc-date-field) { block-size: 2rem; font-size: 0.8125rem; }
|
|
6
|
+
.oc-density-compact :is(.oc-input, .oc-select) { padding-block: 0; }
|
|
7
|
+
.oc-density-compact .oc-textarea { padding-block: 0.375rem; font-size: 0.8125rem; }
|
|
8
|
+
.oc-density-compact .oc-date-field__icon { inline-size: 2rem; font-size: 1rem; }
|
|
9
|
+
.oc-density-compact .oc-date-field__input { font-size: 0.8125rem; }
|
|
10
|
+
.oc-density-compact .oc-btn:not(.oc-btn--icon, .oc-btn--plain, .oc-btn--inline) { padding-block: 0.3125rem; font-size: 0.8125rem; }
|
|
11
|
+
.oc-density-compact .oc-btn--icon { --oc-btn-square: 2rem; }
|
|
12
|
+
.oc-density-compact .oc-field { gap: 0.25rem; }
|
|
13
|
+
.oc-density-compact .oc-field-label { font-size: 0.75rem; }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-form-section.css | Map: section (details) -> summary (icon, title, hint, status, chevron) -> body grid -> invalid state */
|
|
2
|
+
/* Collapsible form section: a long form in named parts that open and close.
|
|
3
|
+
Native <details>; sections with the same name="" keep one open at a time. */
|
|
4
|
+
|
|
5
|
+
.oc-form-section {
|
|
6
|
+
background: var(--oc-card);
|
|
7
|
+
border: 1px solid var(--oc-border);
|
|
8
|
+
border-radius: var(--oc-radius);
|
|
9
|
+
}
|
|
10
|
+
.oc-form-section + .oc-form-section { margin-block-start: 0.75rem; }
|
|
11
|
+
.oc-form-section__summary {
|
|
12
|
+
display: grid;
|
|
13
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: 0.25rem 0.75rem;
|
|
16
|
+
padding: 0.875rem 1rem;
|
|
17
|
+
list-style: none;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
}
|
|
20
|
+
.oc-form-section__summary::-webkit-details-marker { display: none; }
|
|
21
|
+
.oc-form-section__summary:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: -2px; border-radius: var(--oc-radius); }
|
|
22
|
+
.oc-form-section__icon {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
inline-size: 2.25rem;
|
|
27
|
+
block-size: 2.25rem;
|
|
28
|
+
font-size: 1.25rem;
|
|
29
|
+
color: var(--oc-primary);
|
|
30
|
+
background: oklch(from var(--oc-primary) l c h / 0.1);
|
|
31
|
+
border-radius: calc(var(--oc-radius) - 2px);
|
|
32
|
+
}
|
|
33
|
+
.oc-form-section__heading { display: grid; gap: 0.125rem; min-inline-size: 0; }
|
|
34
|
+
.oc-form-section__title { margin: 0; font-size: 0.9375rem; font-weight: 600; }
|
|
35
|
+
.oc-form-section__hint { margin: 0; font-size: 0.8125rem; color: var(--oc-muted-foreground); }
|
|
36
|
+
.oc-form-section__status { font-size: 0.75rem; white-space: nowrap; }
|
|
37
|
+
.oc-form-section__summary::after {
|
|
38
|
+
content: "";
|
|
39
|
+
inline-size: 1.125rem;
|
|
40
|
+
block-size: 1.125rem;
|
|
41
|
+
background: var(--oc-muted-foreground);
|
|
42
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
43
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
44
|
+
transition: rotate var(--oc-transition-fast);
|
|
45
|
+
}
|
|
46
|
+
.oc-form-section[open] > .oc-form-section__summary::after { rotate: 180deg; }
|
|
47
|
+
.oc-form-section__body {
|
|
48
|
+
display: grid;
|
|
49
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
|
|
50
|
+
gap: 1rem;
|
|
51
|
+
padding: 1rem;
|
|
52
|
+
border-block-start: 1px solid var(--oc-border);
|
|
53
|
+
}
|
|
54
|
+
.oc-form-section__body > .oc-form-section__wide { grid-column: 1 / -1; }
|
|
55
|
+
/* A section with an invalid field gets a red edge, also while closed, so an error is never hidden. */
|
|
56
|
+
.oc-form-section:has(:user-invalid, [aria-invalid="true"]) { border-color: var(--oc-destructive); }
|
|
57
|
+
.oc-form-section:has(:user-invalid, [aria-invalid="true"]) .oc-form-section__icon {
|
|
58
|
+
color: var(--oc-destructive);
|
|
59
|
+
background: oklch(from var(--oc-destructive) l c h / 0.1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@media (prefers-reduced-motion: reduce) {
|
|
63
|
+
.oc-form-section__summary::after { transition: none; }
|
|
64
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-quantity.css | Map: frame -> step buttons -> number input (no spinners) -> focus and disabled -> sizes */
|
|
2
|
+
/* Quantity field: minus, a number input and plus in one frame. Without a script it is a normal number input
|
|
3
|
+
(the browser's arrows and keyboard); form-extras.js makes the buttons step within min / max / step. */
|
|
4
|
+
|
|
5
|
+
.oc-quantity {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
inline-size: fit-content;
|
|
8
|
+
justify-self: start;
|
|
9
|
+
align-items: stretch;
|
|
10
|
+
block-size: 2.25rem;
|
|
11
|
+
background: var(--oc-background);
|
|
12
|
+
border: 1px solid var(--oc-input, var(--oc-border));
|
|
13
|
+
border-radius: var(--oc-radius);
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
.oc-quantity__step {
|
|
17
|
+
display: inline-grid;
|
|
18
|
+
place-items: center;
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
inline-size: 2.25rem;
|
|
21
|
+
padding: 0;
|
|
22
|
+
font: inherit;
|
|
23
|
+
color: var(--oc-muted-foreground);
|
|
24
|
+
background: transparent;
|
|
25
|
+
border: 0;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
}
|
|
28
|
+
.oc-quantity__step > :is(svg, iconify-icon) { inline-size: 1rem; block-size: 1rem; font-size: 1rem; }
|
|
29
|
+
.oc-quantity__step:hover { color: var(--oc-foreground); background: var(--oc-accent); }
|
|
30
|
+
.oc-quantity__step:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: -2px; }
|
|
31
|
+
.oc-quantity__step:disabled { opacity: 0.35; cursor: not-allowed; background: transparent; }
|
|
32
|
+
.oc-quantity__input {
|
|
33
|
+
inline-size: 3.25rem;
|
|
34
|
+
min-inline-size: 0;
|
|
35
|
+
padding: 0 0.25rem;
|
|
36
|
+
font: inherit;
|
|
37
|
+
font-size: 0.875rem;
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
font-variant-numeric: tabular-nums;
|
|
40
|
+
text-align: center;
|
|
41
|
+
color: var(--oc-foreground);
|
|
42
|
+
background: transparent;
|
|
43
|
+
border: 0;
|
|
44
|
+
border-inline: 1px solid var(--oc-border);
|
|
45
|
+
-moz-appearance: textfield;
|
|
46
|
+
appearance: textfield;
|
|
47
|
+
}
|
|
48
|
+
.oc-quantity__input::-webkit-outer-spin-button,
|
|
49
|
+
.oc-quantity__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
|
50
|
+
.oc-quantity__input:focus { outline: none; }
|
|
51
|
+
.oc-quantity:has(.oc-quantity__input:focus-visible) { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
|
|
52
|
+
.oc-quantity:has(.oc-quantity__input:disabled) { opacity: 0.5; }
|
|
53
|
+
.oc-quantity--sm { block-size: 2rem; }
|
|
54
|
+
.oc-quantity--sm .oc-quantity__step { inline-size: 2rem; }
|
|
55
|
+
.oc-quantity--sm .oc-quantity__input { inline-size: 2.75rem; font-size: 0.8125rem; }
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-rating.css | Map: star mask -> rating (display) -> sizes -> summary line -> rating input (radios, hover preview) -> focus */
|
|
2
|
+
/* Star rating without JavaScript. .oc-rating shows a score (--oc-rating, halves and decimals work); .oc-rating-input
|
|
3
|
+
is five radio buttons drawn as stars: :has() fills up to the checked or hovered star. */
|
|
4
|
+
|
|
5
|
+
.oc-rating,
|
|
6
|
+
.oc-rating-input {
|
|
7
|
+
--oc-rating-size: 1.125rem;
|
|
8
|
+
--oc-rating-color: var(--oc-warning);
|
|
9
|
+
--oc-rating-empty: oklch(from var(--oc-muted-foreground) l c h / 0.28);
|
|
10
|
+
--oc-rating-star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2L9.19 8.62L2 9.24l5.45 4.73L5.82 21z'/%3E%3C/svg%3E");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Display: one bar of five stars; the fill is the score out of --oc-rating-max (default 5). */
|
|
14
|
+
.oc-rating {
|
|
15
|
+
--oc-rating: 0;
|
|
16
|
+
--oc-rating-max: 5;
|
|
17
|
+
display: inline-block;
|
|
18
|
+
flex-shrink: 0;
|
|
19
|
+
inline-size: calc(var(--oc-rating-size) * var(--oc-rating-max));
|
|
20
|
+
block-size: var(--oc-rating-size);
|
|
21
|
+
vertical-align: middle;
|
|
22
|
+
background: linear-gradient(90deg, var(--oc-rating-color) calc(var(--oc-rating) / var(--oc-rating-max) * 100%), var(--oc-rating-empty) 0);
|
|
23
|
+
-webkit-mask: var(--oc-rating-star) left center / var(--oc-rating-size) var(--oc-rating-size) repeat-x;
|
|
24
|
+
mask: var(--oc-rating-star) left center / var(--oc-rating-size) var(--oc-rating-size) repeat-x;
|
|
25
|
+
}
|
|
26
|
+
.oc-rating--sm, .oc-rating-input.oc-rating-input--sm { --oc-rating-size: 0.875rem; }
|
|
27
|
+
.oc-rating--lg, .oc-rating-input.oc-rating-input--lg { --oc-rating-size: 1.5rem; }
|
|
28
|
+
|
|
29
|
+
/* Summary: stars, the score and the number of reviews on one line. */
|
|
30
|
+
.oc-rating-summary { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; font-size: 0.875rem; }
|
|
31
|
+
.oc-rating-summary__score { font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
32
|
+
.oc-rating-summary__count { color: var(--oc-muted-foreground); }
|
|
33
|
+
a.oc-rating-summary__count { text-decoration: underline; text-underline-offset: 0.2em; }
|
|
34
|
+
a.oc-rating-summary__count:hover { color: var(--oc-foreground); }
|
|
35
|
+
|
|
36
|
+
/* Input: a fieldset with five labels (radio inside). The stars are one masked bar behind the labels. */
|
|
37
|
+
.oc-rating-input {
|
|
38
|
+
position: relative;
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
inline-size: calc(var(--oc-rating-size) * 5 + 0.5rem);
|
|
41
|
+
block-size: calc(var(--oc-rating-size) + 0.5rem);
|
|
42
|
+
margin: 0;
|
|
43
|
+
padding: 0.25rem;
|
|
44
|
+
border: 0;
|
|
45
|
+
border-radius: calc(var(--oc-radius) - 2px);
|
|
46
|
+
}
|
|
47
|
+
.oc-rating-input::before {
|
|
48
|
+
content: "";
|
|
49
|
+
position: absolute;
|
|
50
|
+
inset: 0.25rem;
|
|
51
|
+
background: linear-gradient(90deg, var(--oc-rating-color) calc(var(--oc-rating-hover, var(--oc-rating-pick, 0)) * var(--oc-rating-size)), var(--oc-rating-empty) 0);
|
|
52
|
+
-webkit-mask: var(--oc-rating-star) left center / var(--oc-rating-size) var(--oc-rating-size) repeat-x;
|
|
53
|
+
mask: var(--oc-rating-star) left center / var(--oc-rating-size) var(--oc-rating-size) repeat-x;
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
}
|
|
56
|
+
.oc-rating-input > legend { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
57
|
+
.oc-rating-input > label { position: relative; z-index: 1; flex: 0 0 var(--oc-rating-size); cursor: pointer; }
|
|
58
|
+
.oc-rating-input > label > input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
|
|
59
|
+
.oc-rating-input > label > span { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
60
|
+
|
|
61
|
+
.oc-rating-input:has(label:nth-of-type(1) input:checked) { --oc-rating-pick: 1; }
|
|
62
|
+
.oc-rating-input:has(label:nth-of-type(2) input:checked) { --oc-rating-pick: 2; }
|
|
63
|
+
.oc-rating-input:has(label:nth-of-type(3) input:checked) { --oc-rating-pick: 3; }
|
|
64
|
+
.oc-rating-input:has(label:nth-of-type(4) input:checked) { --oc-rating-pick: 4; }
|
|
65
|
+
.oc-rating-input:has(label:nth-of-type(5) input:checked) { --oc-rating-pick: 5; }
|
|
66
|
+
/* Hover preview (pointer devices only, so a tap does not leave a preview behind). */
|
|
67
|
+
@media (hover: hover) {
|
|
68
|
+
.oc-rating-input:has(label:nth-of-type(1):hover) { --oc-rating-hover: 1; }
|
|
69
|
+
.oc-rating-input:has(label:nth-of-type(2):hover) { --oc-rating-hover: 2; }
|
|
70
|
+
.oc-rating-input:has(label:nth-of-type(3):hover) { --oc-rating-hover: 3; }
|
|
71
|
+
.oc-rating-input:has(label:nth-of-type(4):hover) { --oc-rating-hover: 4; }
|
|
72
|
+
.oc-rating-input:has(label:nth-of-type(5):hover) { --oc-rating-hover: 5; }
|
|
73
|
+
}
|
|
74
|
+
.oc-rating-input:has(input:focus-visible) { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
|
|
75
|
+
.oc-rating-input:has(input:disabled) { opacity: 0.5; }
|
|
76
|
+
.oc-rating-input:has(input:disabled) > label,
|
|
77
|
+
.oc-rating-input:has(input:disabled) > label > input { cursor: not-allowed; }
|
|
78
|
+
|
|
79
|
+
@media (forced-colors: active) {
|
|
80
|
+
.oc-rating, .oc-rating-input::before { --oc-rating-color: CanvasText; --oc-rating-empty: GrayText; forced-color-adjust: none; }
|
|
81
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* File: packages/ui/src/parts/media-attachment.css | Map: list -> file chip (icon by type, name, size, open/remove) -> type tones
|
|
2
2
|
-> uploading progress + error -> image preview (in a message) -> thumbnail chip (in a composer) -> reduced motion
|
|
3
3
|
Attachment: a file chip or an image preview inside a chat message or a composer. The icon tile shows the extension and is
|
|
4
|
-
tinted per type (--pdf, --doc, --sheet, --zip, --image).
|
|
4
|
+
tinted per type (--pdf, --doc, --sheet, --zip, --image). */
|
|
5
5
|
|
|
6
6
|
.oc-attachment-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
|
|
7
7
|
.oc-attachment-list--stack { flex-direction: column; flex-wrap: nowrap; }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/media-barcode.css | Map: barcode figure -> svg bars -> digits -> sizes (hero, compact) -> error */
|
|
2
|
+
/* Barcode display: EAN-13, EAN-8, UPC-A or Code 128, drawn as SVG by
|
|
3
|
+
packages/ui/src/barcode.js from data-value. Always black bars on white, in both themes, so scanners read it. */
|
|
4
|
+
|
|
5
|
+
.oc-barcode {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: 0.25rem;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0.75rem 1rem;
|
|
12
|
+
color: var(--color-black-50);
|
|
13
|
+
background: var(--color-white-50);
|
|
14
|
+
border: 1px solid var(--oc-border);
|
|
15
|
+
border-radius: var(--oc-radius);
|
|
16
|
+
}
|
|
17
|
+
.oc-barcode svg { display: block; inline-size: auto; block-size: var(--oc-barcode-height, 4rem); max-inline-size: 100%; }
|
|
18
|
+
.oc-barcode__digits { font-family: var(--oc-font-mono); font-size: 0.8125rem; letter-spacing: 0.18em; color: inherit; }
|
|
19
|
+
.oc-barcode--hero { padding: 1.25rem 1.5rem; --oc-barcode-height: 6rem; }
|
|
20
|
+
.oc-barcode--hero .oc-barcode__digits { font-size: 1rem; }
|
|
21
|
+
.oc-barcode--compact { padding: 0.375rem 0.5rem; --oc-barcode-height: 2.25rem; }
|
|
22
|
+
.oc-barcode--compact .oc-barcode__digits { font-size: 0.6875rem; letter-spacing: 0.1em; }
|
|
23
|
+
.oc-barcode[data-error] .oc-barcode__digits { color: var(--color-red-70); letter-spacing: 0; }
|
|
@@ -56,6 +56,9 @@
|
|
|
56
56
|
line-height: 1;
|
|
57
57
|
cursor: pointer;
|
|
58
58
|
}
|
|
59
|
+
/* Webicons in the buttons (inline SVG or <iconify-icon>): a fixed size, so nothing is cut off at the round edge. */
|
|
60
|
+
.oc-chat-composer__tool > :is(svg, iconify-icon) { inline-size: 1.25rem; block-size: 1.25rem; font-size: 1.25rem; }
|
|
61
|
+
.oc-chat-composer__send > :is(svg, iconify-icon) { inline-size: 1.125rem; block-size: 1.125rem; font-size: 1.125rem; }
|
|
59
62
|
.oc-chat-composer__tool:hover { background: var(--oc-surface-strong); color: var(--oc-foreground); }
|
|
60
63
|
.oc-chat-composer__tool:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
|
|
61
64
|
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
background: transparent;
|
|
16
16
|
font-size: 1.15rem;
|
|
17
17
|
line-height: 1;
|
|
18
|
+
color: var(--oc-muted-foreground);
|
|
18
19
|
cursor: pointer;
|
|
19
20
|
}
|
|
20
|
-
.oc-emoji-picker__trigger:
|
|
21
|
+
.oc-emoji-picker__trigger > :is(svg, iconify-icon) { inline-size: 1.25rem; block-size: 1.25rem; font-size: 1.25rem; }
|
|
22
|
+
.oc-emoji-picker__trigger:hover { color: var(--oc-foreground); background: var(--oc-surface-strong); }
|
|
21
23
|
.oc-emoji-picker__trigger:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
|
|
22
24
|
|
|
23
25
|
/* The panel: floats above the trigger (implicit anchor of popovertarget), flips when there is no room. */
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/media-gallery.css | Map: gallery grid -> stage and slides (radio + :has, up to 8) -> badge -> thumbnails -> side layout (container query) */
|
|
2
|
+
/* Product gallery without JavaScript: one large photo and a row of thumbnails. Every thumbnail is a label with a
|
|
3
|
+
radio; :has() shows the slide at the same position. Arrow keys move between the photos (native radios). */
|
|
4
|
+
|
|
5
|
+
.oc-gallery { container-type: inline-size; inline-size: 100%; display: grid; gap: 0.75rem; min-inline-size: 0; }
|
|
6
|
+
.oc-gallery__stage {
|
|
7
|
+
position: relative;
|
|
8
|
+
aspect-ratio: var(--oc-gallery-ratio, 4 / 3);
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
background: var(--oc-muted);
|
|
11
|
+
border: 1px solid var(--oc-border);
|
|
12
|
+
border-radius: calc(var(--oc-radius) + 4px);
|
|
13
|
+
}
|
|
14
|
+
.oc-gallery__slide { display: none; margin: 0; block-size: 100%; }
|
|
15
|
+
.oc-gallery__slide > img { display: block; inline-size: 100%; block-size: 100%; object-fit: var(--oc-gallery-fit, contain); }
|
|
16
|
+
.oc-gallery:not(:has(.oc-gallery__thumb input:checked)) .oc-gallery__slide:first-of-type,
|
|
17
|
+
.oc-gallery:has(.oc-gallery__thumb:nth-of-type(1) input:checked) .oc-gallery__slide:nth-of-type(1),
|
|
18
|
+
.oc-gallery:has(.oc-gallery__thumb:nth-of-type(2) input:checked) .oc-gallery__slide:nth-of-type(2),
|
|
19
|
+
.oc-gallery:has(.oc-gallery__thumb:nth-of-type(3) input:checked) .oc-gallery__slide:nth-of-type(3),
|
|
20
|
+
.oc-gallery:has(.oc-gallery__thumb:nth-of-type(4) input:checked) .oc-gallery__slide:nth-of-type(4),
|
|
21
|
+
.oc-gallery:has(.oc-gallery__thumb:nth-of-type(5) input:checked) .oc-gallery__slide:nth-of-type(5),
|
|
22
|
+
.oc-gallery:has(.oc-gallery__thumb:nth-of-type(6) input:checked) .oc-gallery__slide:nth-of-type(6),
|
|
23
|
+
.oc-gallery:has(.oc-gallery__thumb:nth-of-type(7) input:checked) .oc-gallery__slide:nth-of-type(7),
|
|
24
|
+
.oc-gallery:has(.oc-gallery__thumb:nth-of-type(8) input:checked) .oc-gallery__slide:nth-of-type(8) { display: block; }
|
|
25
|
+
.oc-gallery__badge { position: absolute; inset: 0.75rem auto auto 0.75rem; z-index: 1; }
|
|
26
|
+
|
|
27
|
+
.oc-gallery__thumbs { display: flex; gap: 0.5rem; min-inline-size: 0; margin: 0; padding: 0.125rem; overflow-x: auto; border: 0; scrollbar-width: thin; }
|
|
28
|
+
.oc-gallery__thumbs > legend { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
29
|
+
.oc-gallery__thumb {
|
|
30
|
+
position: relative;
|
|
31
|
+
flex: 0 0 auto;
|
|
32
|
+
inline-size: var(--oc-gallery-thumb, 4rem);
|
|
33
|
+
aspect-ratio: 1;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
background: var(--oc-muted);
|
|
36
|
+
border: 2px solid transparent;
|
|
37
|
+
border-radius: var(--oc-radius);
|
|
38
|
+
outline: 1px solid var(--oc-border);
|
|
39
|
+
outline-offset: -1px;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
}
|
|
42
|
+
.oc-gallery__thumb > img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
|
|
43
|
+
.oc-gallery__thumb > input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
|
|
44
|
+
.oc-gallery__thumb:hover { outline-color: var(--oc-muted-foreground); }
|
|
45
|
+
.oc-gallery__thumb:has(input:checked) { border-color: var(--oc-primary); outline-color: transparent; }
|
|
46
|
+
.oc-gallery__thumb:has(input:focus-visible) { box-shadow: 0 0 0 2px var(--oc-background), 0 0 0 4px var(--oc-ring); }
|
|
47
|
+
|
|
48
|
+
/* Side: thumbnails in a column on the left once the gallery itself is 32rem wide or more. The grid is always two
|
|
49
|
+
tracks; below 32rem both children span them, so the query only moves the children (a container cannot query itself). */
|
|
50
|
+
.oc-gallery--side { grid-template-columns: auto minmax(0, 1fr); }
|
|
51
|
+
.oc-gallery--side > * { grid-column: 1 / -1; }
|
|
52
|
+
@container (min-width: 32rem) {
|
|
53
|
+
.oc-gallery--side > .oc-gallery__stage { grid-column: 2; grid-row: 1; }
|
|
54
|
+
.oc-gallery--side > .oc-gallery__thumbs { grid-column: 1; grid-row: 1; flex-direction: column; overflow: visible; }
|
|
55
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/media-glass-liquid.css
|
|
2
|
+
Map: liquid sheet (.oc-glass--liquid: clear tint, strong blur + saturation, inner light, specular rim) -> menu bar
|
|
3
|
+
(.oc-glass-bar: items with icon and label, the current one in a raised bubble) -> icon-only and round button -> fallbacks
|
|
4
|
+
Liquid glass in the style of recent desktop and phone systems, in CSS only: the backdrop is blurred and brightened,
|
|
5
|
+
the edge catches light. Put it over a photo or colourful content; on a plain page it looks like a soft card. */
|
|
6
|
+
|
|
7
|
+
.oc-glass.oc-glass--liquid,
|
|
8
|
+
.oc-glass--liquid,
|
|
9
|
+
.oc-glass-bar,
|
|
10
|
+
.oc-site-header--glass .oc-site-header__inner {
|
|
11
|
+
--oc-liquid-tint: light-dark(oklch(100% 0 0deg / 0.1), oklch(100% 0 0deg / 0.05));
|
|
12
|
+
--oc-liquid-edge: light-dark(oklch(100% 0 0deg / 0.55), oklch(100% 0 0deg / 0.22));
|
|
13
|
+
--oc-liquid-blur: 5px;
|
|
14
|
+
position: relative;
|
|
15
|
+
isolation: isolate;
|
|
16
|
+
background: var(--oc-liquid-tint);
|
|
17
|
+
border: 1px solid var(--oc-liquid-edge);
|
|
18
|
+
/* Light enters at the top left and leaves at the bottom right; a soft glow runs along the inner edge,
|
|
19
|
+
so the sheet reads as a thick, clear lens rather than frosted plastic. */
|
|
20
|
+
box-shadow:
|
|
21
|
+
0 10px 30px -10px oklch(0% 0 0deg / 0.35),
|
|
22
|
+
inset 2px 2px 1px -2px oklch(100% 0 0deg / 0.95),
|
|
23
|
+
inset -2px -2px 1px -2px oklch(100% 0 0deg / 0.7),
|
|
24
|
+
inset 0 0 10px 1px oklch(100% 0 0deg / 0.22),
|
|
25
|
+
inset 0 -8px 16px -12px oklch(100% 0 0deg / 0.35);
|
|
26
|
+
backdrop-filter: blur(var(--oc-liquid-blur)) saturate(1.9) brightness(1.1) contrast(1.05);
|
|
27
|
+
}
|
|
28
|
+
/* Specular rim: bright on the top-left and bottom-right edge, only on the border ring. */
|
|
29
|
+
.oc-glass--liquid::before,
|
|
30
|
+
.oc-glass-bar::before,
|
|
31
|
+
.oc-site-header--glass .oc-site-header__inner::before {
|
|
32
|
+
content: "";
|
|
33
|
+
position: absolute;
|
|
34
|
+
inset: -1px;
|
|
35
|
+
z-index: 1;
|
|
36
|
+
padding: 1.5px;
|
|
37
|
+
border-radius: inherit;
|
|
38
|
+
background: linear-gradient(135deg, oklch(100% 0 0deg / 1), oklch(100% 0 0deg / 0.15) 25%, oklch(100% 0 0deg / 0) 50%, oklch(100% 0 0deg / 0.15) 75%, oklch(100% 0 0deg / 0.8));
|
|
39
|
+
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
40
|
+
mask-composite: exclude;
|
|
41
|
+
pointer-events: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Menu bar: a pill of items, each an icon over a short label. */
|
|
45
|
+
.oc-glass-bar {
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 0.2rem;
|
|
49
|
+
padding: 0.35rem;
|
|
50
|
+
border-radius: 999px;
|
|
51
|
+
}
|
|
52
|
+
.oc-glass-bar__item {
|
|
53
|
+
position: relative;
|
|
54
|
+
display: grid;
|
|
55
|
+
justify-items: center;
|
|
56
|
+
gap: 0.15rem;
|
|
57
|
+
min-inline-size: 4.25rem;
|
|
58
|
+
padding: 0.45rem 0.85rem;
|
|
59
|
+
border: 0;
|
|
60
|
+
border-radius: 999px;
|
|
61
|
+
color: light-dark(oklch(22% 0.02 260deg / 0.78), oklch(100% 0 0deg / 0.82));
|
|
62
|
+
background: none;
|
|
63
|
+
font: inherit;
|
|
64
|
+
font-size: 0.7rem;
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
line-height: 1.1;
|
|
67
|
+
text-decoration: none;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
transition: background-color 0.25s, color 0.25s, box-shadow 0.25s, scale 0.2s;
|
|
70
|
+
}
|
|
71
|
+
.oc-glass-bar__item > :is(svg, iconify-icon) { inline-size: 1.4rem; block-size: 1.4rem; font-size: 1.4rem; }
|
|
72
|
+
.oc-glass-bar__item:hover { background: light-dark(oklch(100% 0 0deg / 0.28), oklch(100% 0 0deg / 0.08)); }
|
|
73
|
+
.oc-glass-bar__item:active { scale: 0.94; }
|
|
74
|
+
.oc-glass-bar__item:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
|
|
75
|
+
/* The current item sits in a raised, lighter bubble. */
|
|
76
|
+
.oc-glass-bar__item:is([aria-current="page"], [aria-pressed="true"], :has(> input:checked)) {
|
|
77
|
+
color: light-dark(var(--oc-primary), var(--color-white-50));
|
|
78
|
+
background: light-dark(oklch(100% 0 0deg / 0.62), oklch(100% 0 0deg / 0.24));
|
|
79
|
+
box-shadow: inset 0 1px 1px oklch(100% 0 0deg / 0.85), 0 6px 14px -8px oklch(0% 0 0deg / 0.4);
|
|
80
|
+
}
|
|
81
|
+
.oc-glass-bar__item > input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
|
|
82
|
+
.oc-glass-bar__item:has(> input:focus-visible) { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
|
|
83
|
+
|
|
84
|
+
/* Icons only, and a separate round button next to the bar (search, compose). */
|
|
85
|
+
.oc-glass-bar--icons .oc-glass-bar__item { min-inline-size: 0; padding: 0.6rem; }
|
|
86
|
+
.oc-glass-bar--round { padding: 0.35rem; border-radius: 999px; }
|
|
87
|
+
.oc-glass-bar--round .oc-glass-bar__item { min-inline-size: 0; padding: 0.6rem; }
|
|
88
|
+
.oc-glass-bar__sep { align-self: stretch; inline-size: 1px; margin: 0.4rem 0.15rem; background: var(--oc-liquid-edge); }
|
|
89
|
+
|
|
90
|
+
/* Less transparency asked for, or no backdrop-filter: a solid bar that still reads as one. */
|
|
91
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
92
|
+
.oc-glass--liquid, .oc-glass-bar { --oc-liquid-tint: var(--oc-card); backdrop-filter: none; }
|
|
93
|
+
}
|
|
94
|
+
@supports not (backdrop-filter: blur(1px)) {
|
|
95
|
+
.oc-glass--liquid, .oc-glass-bar { --oc-liquid-tint: light-dark(oklch(100% 0 0deg / 0.85), oklch(25% 0.02 260deg / 0.9)); }
|
|
96
|
+
}
|
|
97
|
+
@media (prefers-reduced-motion: reduce) {
|
|
98
|
+
.oc-glass-bar__item { transition: none; }
|
|
99
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/media-glass-options.css
|
|
2
|
+
Map: amount (--clear: more see-through, --frost: more frosted) -> light content on a colourful backdrop -> stage (moving colour blobs to show it off)
|
|
3
|
+
Extras for .oc-glass, .oc-glass--liquid and .oc-glass-bar (media-glass.css, media-glass-liquid.css). The selectors
|
|
4
|
+
carry two classes so they win over the base glass whatever order the parts load in. */
|
|
5
|
+
|
|
6
|
+
/* Amount of glass: --clear lets more of the backdrop through, --frost hides more of it. */
|
|
7
|
+
:is(.oc-glass, .oc-glass-bar).oc-glass--clear {
|
|
8
|
+
--oc-glass-sheet: light-dark(oklch(from var(--oc-background) l c h / 0.32), oklch(from var(--oc-background) l c h / 0.26));
|
|
9
|
+
--oc-glass-blur: 10px;
|
|
10
|
+
--oc-liquid-tint: light-dark(oklch(100% 0 0deg / 0.05), oklch(100% 0 0deg / 0.03));
|
|
11
|
+
--oc-liquid-blur: 2px;
|
|
12
|
+
}
|
|
13
|
+
:is(.oc-glass, .oc-glass-bar).oc-glass--frost {
|
|
14
|
+
--oc-glass-sheet: light-dark(oklch(from var(--oc-background) l c h / 0.86), oklch(from var(--oc-background) l c h / 0.8));
|
|
15
|
+
--oc-glass-blur: 40px;
|
|
16
|
+
--oc-liquid-tint: light-dark(oklch(100% 0 0deg / 0.38), oklch(100% 0 0deg / 0.12));
|
|
17
|
+
--oc-liquid-blur: 18px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/* Light content: white icons and labels for glass over a dark or very colourful backdrop. */
|
|
22
|
+
.oc-glass-bar.oc-glass-bar--light .oc-glass-bar__item {
|
|
23
|
+
color: oklch(100% 0 0deg / 0.92);
|
|
24
|
+
text-shadow: 0 1px 2px oklch(0% 0 0deg / 0.25);
|
|
25
|
+
}
|
|
26
|
+
.oc-glass-bar.oc-glass-bar--light .oc-glass-bar__item:is([aria-current="page"], [aria-pressed="true"], :has(> input:checked)) {
|
|
27
|
+
color: var(--color-white-50);
|
|
28
|
+
background: oklch(100% 0 0deg / 0.2);
|
|
29
|
+
}
|
|
30
|
+
.oc-glass-bar.oc-glass-bar--light .oc-glass-bar__item:hover { background: oklch(100% 0 0deg / 0.12); }
|
|
31
|
+
|
|
32
|
+
/* Stage: round colour shapes that drift slowly, so the change through the glass shows. A background for a hero or a demo. */
|
|
33
|
+
.oc-glass-stage {
|
|
34
|
+
position: relative;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
background:
|
|
37
|
+
radial-gradient(circle at 22% 72%, var(--color-magenta-50) 0 16%, transparent 16.5%),
|
|
38
|
+
radial-gradient(circle at 82% 68%, var(--color-orange-50) 0 18%, transparent 18.5%),
|
|
39
|
+
radial-gradient(circle at 58% 18%, var(--color-skycyan-50) 0 13%, transparent 13.5%),
|
|
40
|
+
radial-gradient(60% 70% at 30% 30%, var(--color-magenta-60), transparent 70%),
|
|
41
|
+
linear-gradient(120deg, var(--color-purple-60), var(--color-violet-80));
|
|
42
|
+
background-size: 140% 140%;
|
|
43
|
+
animation: oc-glass-stage 18s ease-in-out infinite alternate;
|
|
44
|
+
}
|
|
45
|
+
@keyframes oc-glass-stage {
|
|
46
|
+
to { background-position: 100% 60%; }
|
|
47
|
+
}
|
|
48
|
+
@media (prefers-reduced-motion: reduce) {
|
|
49
|
+
.oc-glass-stage { animation: none; }
|
|
50
|
+
}
|