@xoong/ui 0.0.0-stage → 0.0.34
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 +21 -0
- package/README.md +30 -2
- package/package.json +51 -4
- package/src/confirm.js +53 -0
- package/src/dialog.js +71 -0
- package/src/form.js +148 -0
- package/src/index.d.ts +33 -0
- package/src/index.js +10 -0
- package/src/mist.css +1774 -0
- package/src/router.js +361 -0
- package/src/table.js +79 -0
- package/src/toast.js +81 -0
- package/src/xoong.css +149 -0
package/src/xoong.css
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xoong.css: design tokens and the default look of the admin set. Plain CSS, no build step.
|
|
3
|
+
*
|
|
4
|
+
* <link rel="stylesheet" href="/@xoong/ui/src/xoong.css">
|
|
5
|
+
*
|
|
6
|
+
* Everything lives in cascade layers, so your own unlayered CSS always wins without specificity fights.
|
|
7
|
+
* Change the look by overriding the custom properties on :root (or on any element, for a region):
|
|
8
|
+
*
|
|
9
|
+
* :root { --xo-color-accent: #0a7; --xo-radius: 0; }
|
|
10
|
+
*/
|
|
11
|
+
@layer xoong.tokens, xoong.base, xoong.components;
|
|
12
|
+
|
|
13
|
+
@layer xoong.tokens {
|
|
14
|
+
:root {
|
|
15
|
+
color-scheme: light dark;
|
|
16
|
+
--xo-font: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
17
|
+
--xo-font-mono: ui-monospace, "SF Mono", Menlo, monospace;
|
|
18
|
+
--xo-text: 1rem;
|
|
19
|
+
--xo-text-small: 0.875rem;
|
|
20
|
+
--xo-space-1: 0.25rem;
|
|
21
|
+
--xo-space-2: 0.5rem;
|
|
22
|
+
--xo-space-3: 1rem;
|
|
23
|
+
--xo-space-4: 1.5rem;
|
|
24
|
+
--xo-radius: 0.375rem;
|
|
25
|
+
--xo-form-max-width: 28rem; /* generated forms; `none` for editors that should use the whole width */
|
|
26
|
+
|
|
27
|
+
--xo-color-bg: light-dark(#f7f8fa, #14161a);
|
|
28
|
+
--xo-color-surface: light-dark(#fff, #1d2026);
|
|
29
|
+
--xo-color-text: light-dark(#1b1f27, #e8eaee);
|
|
30
|
+
--xo-color-muted: light-dark(#5b6472, #9aa3b2);
|
|
31
|
+
--xo-color-border: light-dark(#d9dde4, #343a46);
|
|
32
|
+
--xo-color-control-border: light-dark(#838c9b, #737d90); /* the edge of a button, field or box that holds input: 3:1 against the page (WCAG 1.4.11) */
|
|
33
|
+
--xo-color-accent: light-dark(#2457d6, #7aa2ff);
|
|
34
|
+
--xo-color-accent-text: light-dark(#fff, #0d1117);
|
|
35
|
+
--xo-color-danger: light-dark(#b42318, #ff8a80);
|
|
36
|
+
--xo-color-accent-soft: color-mix(in srgb, var(--xo-color-accent) 14%, var(--xo-color-surface)); /* a tint of the accent: the current item, a selected row, a chip */
|
|
37
|
+
--xo-color-subtle: color-mix(in srgb, var(--xo-color-text) 5%, var(--xo-color-surface)); /* a quiet surface: table header, tab bar, toolbar */
|
|
38
|
+
--xo-color-success: light-dark(#1a7f37, #56d364);
|
|
39
|
+
--xo-color-warning: light-dark(#9a6700, #e3b341);
|
|
40
|
+
--xo-color-inverse: light-dark(#1b1f27, #e8eaee); /* tooltip, toast */
|
|
41
|
+
--xo-color-inverse-text: light-dark(#fff, #14161a);
|
|
42
|
+
--xo-focus-thin: 2px solid var(--xo-color-accent); /* a ring drawn inside a list item */
|
|
43
|
+
--xo-focus-inverse: 3px solid var(--xo-color-inverse-text); /* on an inverse surface (toast, tooltip): the accent ring can fall under 3:1 there */
|
|
44
|
+
--xo-focus: 3px solid var(--xo-color-accent); /* the accent must be 3:1 against the page (WCAG 1.4.11, 2.4.11) */
|
|
45
|
+
--xo-control-height: 2.5rem; /* every button, field and select; the -sm one is for dense toolbars and table rows */
|
|
46
|
+
--xo-control-height-sm: 2rem;
|
|
47
|
+
--xo-control-height-lg: 3rem;
|
|
48
|
+
--xo-control-padding: 0.9rem;
|
|
49
|
+
--xo-control-gap: 0.5rem; /* between an icon and a label, between controls in a toolbar */
|
|
50
|
+
--xo-touch: 2.75rem; /* the smallest comfortable target: set --xo-control-height to it on a touch layout */
|
|
51
|
+
--xo-field-gap: 0.4rem; /* label, control, hint */
|
|
52
|
+
--xo-icon: 1.25rem;
|
|
53
|
+
--xo-shadow-pop: 0 14px 34px -14px color-mix(in srgb, var(--xo-color-text) 55%, transparent), 0 2px 6px color-mix(in srgb, var(--xo-color-text) 10%, transparent); /* menus, popovers */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@layer xoong.base {
|
|
58
|
+
:where(body) { margin: 0; font: var(--xo-text) / 1.5 var(--xo-font); color: var(--xo-color-text); background: var(--xo-color-bg); }
|
|
59
|
+
:where(h1, h2, h3) { line-height: 1.25; }
|
|
60
|
+
:where(a) { color: var(--xo-color-accent); }
|
|
61
|
+
:where(:focus-visible) { outline: var(--xo-focus); outline-offset: 2px; }
|
|
62
|
+
:where(button) {
|
|
63
|
+
font: inherit; min-height: var(--xo-control-height); padding: 0 var(--xo-control-padding); box-sizing: border-box; cursor: pointer;
|
|
64
|
+
color: var(--xo-color-text); background: var(--xo-color-surface);
|
|
65
|
+
border: 1px solid var(--xo-color-control-border); border-radius: var(--xo-radius);
|
|
66
|
+
}
|
|
67
|
+
:where(button:hover) { border-color: var(--xo-color-accent); }
|
|
68
|
+
:where(button[aria-busy="true"]) { opacity: 0.6; cursor: progress; }
|
|
69
|
+
:where(input, select, textarea) {
|
|
70
|
+
font: inherit; padding: var(--xo-space-1) var(--xo-space-2); color: inherit; background: var(--xo-color-surface);
|
|
71
|
+
border: 1px solid var(--xo-color-control-border); border-radius: var(--xo-radius);
|
|
72
|
+
}
|
|
73
|
+
:where(input:not([type="checkbox"], [type="radio"], [type="color"], [type="range"], [type="file"]), select) { min-height: var(--xo-control-height); box-sizing: border-box; }
|
|
74
|
+
:where([aria-invalid="true"]) { border-color: var(--xo-color-danger); }
|
|
75
|
+
.xo-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@layer xoong.components {
|
|
79
|
+
/*
|
|
80
|
+
* Admin shell: put class="xo-admin" on <body> (or on <xo-router> itself) and give the layout a <header>, a <nav> and a <main xo-outlet>.
|
|
81
|
+
* Both the plain page and the router form work:
|
|
82
|
+
*
|
|
83
|
+
* <body class="xo-admin"> <body>
|
|
84
|
+
* <header>..</header><nav>..</nav><main>..</main> <xo-router class="xo-admin">
|
|
85
|
+
* </body> <template xo-layout="shell"><header>..</header><nav>..</nav><main xo-outlet></main></template> ...
|
|
86
|
+
*
|
|
87
|
+
* The router's wrapper elements (<xo-router>, .xo-router-root, .xo-layout) do not generate boxes (display: contents), so the layout's
|
|
88
|
+
* header, nav and main are grid items of the shell. Nested layouts (a <main> inside the outlet) are not touched by these rules.
|
|
89
|
+
*/
|
|
90
|
+
xo-router, .xo-router-root, .xo-layout { display: contents; }
|
|
91
|
+
.xo-admin, xo-router.xo-admin { display: grid; min-height: 100vh; grid-template: "header header" auto "nav main" 1fr / 14rem 1fr; }
|
|
92
|
+
:is(.xo-admin, .xo-admin > xo-router, .xo-admin > .xo-router-root, .xo-admin > xo-router > .xo-router-root, .xo-admin > .xo-router-root > .xo-layout, .xo-admin > xo-router > .xo-router-root > .xo-layout) > header { grid-area: header; padding: var(--xo-space-2) var(--xo-space-4); background: var(--xo-color-surface); border-bottom: 1px solid var(--xo-color-border); }
|
|
93
|
+
:is(.xo-admin, .xo-admin > xo-router, .xo-admin > .xo-router-root, .xo-admin > xo-router > .xo-router-root, .xo-admin > .xo-router-root > .xo-layout, .xo-admin > xo-router > .xo-router-root > .xo-layout) > nav { grid-area: nav; padding: var(--xo-space-3); background: var(--xo-color-surface); border-right: 1px solid var(--xo-color-border); }
|
|
94
|
+
:is(.xo-admin, .xo-admin > xo-router, .xo-admin > .xo-router-root, .xo-admin > xo-router > .xo-router-root, .xo-admin > .xo-router-root > .xo-layout, .xo-admin > xo-router > .xo-router-root > .xo-layout) > nav :where(a) { display: block; padding: var(--xo-space-1) var(--xo-space-2); border-radius: var(--xo-radius); text-decoration: none; color: inherit; }
|
|
95
|
+
:is(.xo-admin, .xo-admin > xo-router, .xo-admin > .xo-router-root, .xo-admin > xo-router > .xo-router-root, .xo-admin > .xo-router-root > .xo-layout, .xo-admin > xo-router > .xo-router-root > .xo-layout) > nav :where(a[aria-current="page"]) { background: var(--xo-color-bg); font-weight: 600; }
|
|
96
|
+
:is(.xo-admin, .xo-admin > xo-router, .xo-admin > .xo-router-root, .xo-admin > xo-router > .xo-router-root, .xo-admin > .xo-router-root > .xo-layout, .xo-admin > xo-router > .xo-router-root > .xo-layout) > main { grid-area: main; padding: var(--xo-space-4); min-width: 0; }
|
|
97
|
+
@media (max-width: 40rem) {
|
|
98
|
+
.xo-admin, xo-router.xo-admin { grid-template: "header" auto "nav" auto "main" 1fr / 1fr; }
|
|
99
|
+
:is(.xo-admin, .xo-admin > xo-router, .xo-admin > .xo-router-root, .xo-admin > xo-router > .xo-router-root, .xo-admin > .xo-router-root > .xo-layout, .xo-admin > xo-router > .xo-router-root > .xo-layout) > nav { border-right: 0; border-bottom: 1px solid var(--xo-color-border); }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.xo-table { position: relative; overflow-x: auto; background: var(--xo-color-surface); border: 1px solid var(--xo-color-border); border-radius: var(--xo-radius); }
|
|
103
|
+
.xo-table table { width: 100%; border-collapse: collapse; }
|
|
104
|
+
.xo-table :where(th, td) { padding: var(--xo-space-2) var(--xo-space-3); text-align: start; border-bottom: 1px solid var(--xo-color-border); }
|
|
105
|
+
.xo-table :where(th) { font-size: var(--xo-text-small); color: var(--xo-color-muted); font-weight: 600; }
|
|
106
|
+
.xo-table :where(th button) { all: unset; cursor: pointer; font: inherit; }
|
|
107
|
+
.xo-table :where(th button:focus-visible) { outline: var(--xo-focus); }
|
|
108
|
+
.xo-table :where(tbody tr:last-child td) { border-bottom: 0; }
|
|
109
|
+
.xo-table tbody { transition: opacity 0.15s ease; }
|
|
110
|
+
xo-view[data-refreshing] .xo-table tbody { opacity: 0.6; } /* the rows of the last result stay while the next loads: no collapse, no jump */
|
|
111
|
+
.xo-table .xo-actions { text-align: end; white-space: nowrap; } /* a table cell stays a cell (display: flex dropped its border) */
|
|
112
|
+
.xo-table .xo-actions > * + * { margin-inline-start: var(--xo-space-2); }
|
|
113
|
+
.xo-bulk { display: flex; align-items: center; gap: var(--xo-space-2); padding: var(--xo-space-2); background: var(--xo-color-bg); border-bottom: 1px solid var(--xo-color-border); }
|
|
114
|
+
.xo-filters { display: flex; flex-wrap: wrap; gap: var(--xo-space-2); padding: var(--xo-space-2); border-bottom: 1px solid var(--xo-color-border); }
|
|
115
|
+
.xo-toast { position: fixed; inset-block-end: var(--xo-space-4); inset-inline-end: var(--xo-space-4); max-width: 24rem; margin: 0; padding: var(--xo-space-2) var(--xo-space-3); color: var(--xo-color-danger); background: var(--xo-color-surface); border: 1px solid var(--xo-color-danger); border-radius: var(--xo-radius); }
|
|
116
|
+
.xo-toast[hidden] { display: none; }
|
|
117
|
+
.xo-pager { display: flex; gap: var(--xo-space-2); justify-content: flex-end; padding: var(--xo-space-2); }
|
|
118
|
+
|
|
119
|
+
.xo-form { display: grid; gap: var(--xo-space-3); max-width: var(--xo-form-max-width); }
|
|
120
|
+
.xo-object, .xo-array, .xo-group { margin: 0; padding: var(--xo-space-3); border: 1px solid var(--xo-color-border); border-radius: var(--xo-radius); display: grid; gap: var(--xo-space-3); }
|
|
121
|
+
.xo-group { border: 0; padding: 0; }
|
|
122
|
+
.xo-row { display: grid; gap: var(--xo-space-2); padding: var(--xo-space-2); border: 1px dashed var(--xo-color-border); border-radius: var(--xo-radius); }
|
|
123
|
+
.xo-row-actions, .xo-array-add { display: flex; gap: var(--xo-space-2); flex-wrap: wrap; }
|
|
124
|
+
.xo-row-title { font-weight: 600; }
|
|
125
|
+
.xo-handle { cursor: grab; user-select: none; padding: 0 var(--xo-space-1); opacity: .6; align-self: center; }
|
|
126
|
+
.xo-row.xo-dragging { opacity: .5; }
|
|
127
|
+
.xo-row.xo-drop { outline: 2px dashed var(--xo-color-accent, currentColor); outline-offset: 2px; }
|
|
128
|
+
.xo-hint { color: var(--xo-color-muted); font-size: var(--xo-text-small); }
|
|
129
|
+
.xo-object, .xo-array { margin: 0; padding: var(--xo-space-3); border: 1px solid var(--xo-color-border); border-radius: var(--xo-radius); display: grid; gap: var(--xo-space-3); }
|
|
130
|
+
.xo-group { margin: 0; padding: 0; border: 0; display: grid; gap: var(--xo-space-3); }
|
|
131
|
+
.xo-row { display: grid; gap: var(--xo-space-2); padding: var(--xo-space-2); border: 1px dashed var(--xo-color-border); border-radius: var(--xo-radius); }
|
|
132
|
+
.xo-row-actions, .xo-array-add { display: flex; gap: var(--xo-space-2); flex-wrap: wrap; }
|
|
133
|
+
.xo-hint { color: var(--xo-color-muted); font-size: var(--xo-text-small); }
|
|
134
|
+
.xo-field { display: grid; gap: var(--xo-space-1); }
|
|
135
|
+
.xo-field:has(input[type="checkbox"]) { display: flex; align-items: center; gap: var(--xo-space-2); }
|
|
136
|
+
.xo-field:has(> :is(input, select, textarea, xo-combobox, xo-tags, xo-richtext)[required]) > :first-child::after { content: " *"; color: var(--xo-color-danger); } /* a field that must be filled in */
|
|
137
|
+
.xo-error { color: var(--xo-color-danger); font-size: var(--xo-text-small); }
|
|
138
|
+
.xo-form > .xo-submit { justify-self: start; color: var(--xo-color-accent-text); background: var(--xo-color-accent); border-color: transparent; }
|
|
139
|
+
|
|
140
|
+
.xo-confirm { max-width: 26rem; padding: var(--xo-space-4); color: inherit; background: var(--xo-color-surface); border: 1px solid var(--xo-color-border); border-radius: var(--xo-radius); }
|
|
141
|
+
.xo-confirm::backdrop { background: rgb(0 0 0 / 0.45); }
|
|
142
|
+
.xo-confirm-actions { display: flex; gap: var(--xo-space-2); justify-content: flex-end; margin-top: var(--xo-space-3); }
|
|
143
|
+
.xo-confirm-ok { color: var(--xo-color-accent-text); background: var(--xo-color-danger); border-color: transparent; }
|
|
144
|
+
.xo-toast :where(button:focus-visible) { outline: var(--xo-focus-inverse); }
|
|
145
|
+
.xo-toast-action { margin-inline-start: var(--xo-space-2); }
|
|
146
|
+
.xo-toast-status { color: var(--xo-color-text); border-color: var(--xo-color-border); }
|
|
147
|
+
xo-dialog > dialog { padding: var(--xo-space-4); color: inherit; background: var(--xo-color-surface); border: 1px solid var(--xo-color-border); border-radius: var(--xo-radius); }
|
|
148
|
+
xo-dialog > dialog::backdrop { background: rgb(0 0 0 / 0.45); }
|
|
149
|
+
}
|