@webority/theme 0.3.0 → 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.
@@ -0,0 +1,275 @@
1
+ // Text-scale utilities.
2
+ //
3
+ // Part of the wui-components layer — see _wui-components.scss for the whole
4
+ // picture and the load order. Order within the layer is LOAD-BEARING: these
5
+ // partials are a split of one stylesheet, not independent modules, so a rule
6
+ // here may be overridden by a later partial. Never reorder the imports.
7
+
8
+ // ── Text-scale utilities (lx-text-*) ──
9
+ .lx-text-xs {
10
+ font-size: 12px;
11
+ line-height: 1.4;
12
+ }
13
+ .lx-text-sm {
14
+ font-size: 13.5px;
15
+ line-height: 1.5;
16
+ }
17
+ .lx-text-base {
18
+ font-size: 15px;
19
+ line-height: 1.5;
20
+ }
21
+ .lx-text-lg {
22
+ font-size: 17px;
23
+ line-height: 1.4;
24
+ }
25
+ .lx-text-xl {
26
+ font-size: 20px;
27
+ line-height: 1.4;
28
+ }
29
+ .lx-text-2xl {
30
+ font-size: 24px;
31
+ line-height: 1.3;
32
+ }
33
+ .lx-text-3xl {
34
+ font-size: 30px;
35
+ line-height: 1.2;
36
+ }
37
+ .lx-text-4xl {
38
+ font-size: 36px;
39
+ line-height: 1.15;
40
+ }
41
+
42
+ /* ── Chip (token / tag) ── */
43
+ .chip {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: 6px;
47
+ max-width: 100%;
48
+ padding: 4px 10px;
49
+ border: 1px solid var(--color-border);
50
+ border-radius: 999px;
51
+ background: var(--color-bg-muted);
52
+ color: var(--color-text);
53
+ font-size: 0.8125rem;
54
+ font-weight: 500;
55
+ line-height: 1.2;
56
+ vertical-align: middle;
57
+ }
58
+ .chip-sm {
59
+ gap: 4px;
60
+ padding: 2px 8px;
61
+ font-size: 0.75rem;
62
+ }
63
+ .chip-label {
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ white-space: nowrap;
67
+ }
68
+ .chip-ic {
69
+ display: inline-flex;
70
+ color: var(--color-text-subtle);
71
+ }
72
+ .chip-avatar {
73
+ display: inline-flex;
74
+ width: 18px;
75
+ height: 18px;
76
+ margin-left: -2px;
77
+ border-radius: 999px;
78
+ overflow: hidden;
79
+ flex: none;
80
+ }
81
+ .chip-avatar img {
82
+ width: 100%;
83
+ height: 100%;
84
+ object-fit: cover;
85
+ }
86
+ .chip-body {
87
+ display: inline-flex;
88
+ align-items: center;
89
+ gap: inherit;
90
+ padding: 0;
91
+ border: 0;
92
+ background: none;
93
+ font: inherit;
94
+ color: inherit;
95
+ cursor: pointer;
96
+ }
97
+ .chip-clickable {
98
+ cursor: pointer;
99
+ }
100
+ .chip-clickable:hover {
101
+ border-color: var(--color-border-strong);
102
+ }
103
+ .chip-remove {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ margin: 0 -4px 0 2px;
108
+ padding: 2px;
109
+ border: 0;
110
+ border-radius: 999px;
111
+ background: none;
112
+ color: var(--color-text-subtle);
113
+ line-height: 0;
114
+ cursor: pointer;
115
+ }
116
+ .chip-remove:hover {
117
+ background: color-mix(in srgb, var(--color-text) 8%, transparent);
118
+ color: var(--color-text);
119
+ }
120
+ .chip.is-disabled {
121
+ opacity: 0.55;
122
+ pointer-events: none;
123
+ }
124
+
125
+ /* Tones — tinted surface + on-tint text; icon/remove inherit the tone colour. */
126
+ .chip.primary {
127
+ border-color: transparent;
128
+ background: var(--color-primary-soft);
129
+ color: var(--color-primary);
130
+ }
131
+ .chip.success {
132
+ border-color: transparent;
133
+ background: var(--color-success-soft);
134
+ color: var(--color-success-active);
135
+ }
136
+ .chip.warning {
137
+ border-color: transparent;
138
+ background: var(--color-warning-soft);
139
+ color: var(--color-warning-active);
140
+ }
141
+ .chip.danger {
142
+ border-color: transparent;
143
+ background: var(--color-danger-soft);
144
+ color: var(--color-danger-active);
145
+ }
146
+ .chip.info {
147
+ border-color: transparent;
148
+ background: var(--color-info-soft);
149
+ color: var(--color-info-active);
150
+ }
151
+ .chip.primary .chip-ic,
152
+ .chip.success .chip-ic,
153
+ .chip.warning .chip-ic,
154
+ .chip.danger .chip-ic,
155
+ .chip.info .chip-ic,
156
+ .chip.primary .chip-remove,
157
+ .chip.success .chip-remove,
158
+ .chip.warning .chip-remove,
159
+ .chip.danger .chip-remove,
160
+ .chip.info .chip-remove {
161
+ color: currentColor;
162
+ }
163
+ .chip.primary .chip-remove:hover,
164
+ .chip.success .chip-remove:hover,
165
+ .chip.warning .chip-remove:hover,
166
+ .chip.danger .chip-remove:hover,
167
+ .chip.info .chip-remove:hover {
168
+ background: color-mix(in srgb, currentColor 16%, transparent);
169
+ color: currentColor;
170
+ }
171
+
172
+ /* ── Autocomplete (input-first combobox; extends .search-select) ── */
173
+ .search-select-ac {
174
+ justify-content: flex-start;
175
+ cursor: text;
176
+ padding-right: 8px;
177
+ }
178
+ .search-select-ac-input {
179
+ flex: 1 1 auto;
180
+ min-width: 0;
181
+ padding: 0;
182
+ border: 0;
183
+ background: none;
184
+ color: inherit;
185
+ font: inherit;
186
+ outline: none;
187
+ }
188
+ .search-select-ac-input::placeholder {
189
+ color: var(--color-text-subtle);
190
+ }
191
+ .search-select-spinner {
192
+ flex: none;
193
+ width: 1rem;
194
+ height: 1rem;
195
+ color: var(--color-text-subtle);
196
+ border-width: 2px;
197
+ }
198
+ .search-select-hint {
199
+ padding: 10px 12px;
200
+ color: var(--color-text-subtle);
201
+ font-size: 0.8125rem;
202
+ }
203
+
204
+ /* ── Multi-select (chips in the control; check-list dropdown; extends .search-select) ── */
205
+ .search-select-multi {
206
+ align-items: flex-start;
207
+ gap: 6px;
208
+ min-height: 40px;
209
+ height: auto;
210
+ padding-top: 5px;
211
+ padding-bottom: 5px;
212
+ flex-wrap: nowrap;
213
+ }
214
+ .ms-chips {
215
+ display: flex;
216
+ flex-wrap: wrap;
217
+ gap: 6px;
218
+ flex: 1 1 auto;
219
+ min-width: 0;
220
+ align-items: center;
221
+ }
222
+ .search-select-multi .search-select-caret {
223
+ align-self: center;
224
+ }
225
+ .ms-check {
226
+ display: inline-flex;
227
+ align-items: center;
228
+ justify-content: center;
229
+ width: 16px;
230
+ flex: none;
231
+ color: var(--color-primary);
232
+ opacity: 0;
233
+ }
234
+ .search-select-option[aria-selected='true'] .ms-check {
235
+ opacity: 1;
236
+ }
237
+ .ms-opt-label {
238
+ min-width: 0;
239
+ overflow: hidden;
240
+ text-overflow: ellipsis;
241
+ }
242
+ .search-select-multi .search-select-option {
243
+ display: flex;
244
+ align-items: center;
245
+ gap: 8px;
246
+ }
247
+
248
+ /* ── Choice card: image / custom-template variant ── */
249
+ .struct-card-img-card {
250
+ flex-direction: column;
251
+ align-items: stretch;
252
+ padding: 0;
253
+ overflow: hidden;
254
+ }
255
+ .struct-card-media {
256
+ display: block;
257
+ width: 100%;
258
+ aspect-ratio: 16 / 9;
259
+ background: var(--color-bg-muted);
260
+ overflow: hidden;
261
+ }
262
+ .struct-card-img {
263
+ width: 100%;
264
+ height: 100%;
265
+ object-fit: cover;
266
+ display: block;
267
+ }
268
+ .struct-card-img-card .struct-card-body {
269
+ padding: 12px 14px;
270
+ }
271
+ .struct-card-img-card .struct-card-check {
272
+ position: absolute;
273
+ top: 8px;
274
+ right: 8px;
275
+ }
@@ -0,0 +1,340 @@
1
+ // Grid utils, info tip, page header, row actions, spinner.
2
+ //
3
+ // Part of the wui-components layer — see _wui-components.scss for the whole
4
+ // picture and the load order. Order within the layer is LOAD-BEARING: these
5
+ // partials are a split of one stylesheet, not independent modules, so a rule
6
+ // here may be overridden by a later partial. Never reorder the imports.
7
+
8
+ // ── Grid / layout utils ──
9
+ // Responsive card grids (Bootstrap has no grid-template utility).
10
+ .lx-grid {
11
+ display: grid;
12
+ grid-template-columns: 1fr;
13
+ }
14
+ .lx-cols-2 {
15
+ grid-template-columns: repeat(2, minmax(0, 1fr));
16
+ }
17
+ .lx-cols-3 {
18
+ grid-template-columns: repeat(3, minmax(0, 1fr));
19
+ }
20
+ .lx-cols-4 {
21
+ grid-template-columns: repeat(4, minmax(0, 1fr));
22
+ }
23
+ @media (min-width: 768px) {
24
+ .lx-grid-md-2 {
25
+ grid-template-columns: repeat(2, minmax(0, 1fr));
26
+ }
27
+ .lx-grid-md-3 {
28
+ grid-template-columns: repeat(3, minmax(0, 1fr));
29
+ }
30
+ .lx-grid-md-4 {
31
+ grid-template-columns: repeat(4, minmax(0, 1fr));
32
+ }
33
+ }
34
+ @media (min-width: 992px) {
35
+ .lx-grid-lg-2 {
36
+ grid-template-columns: repeat(2, minmax(0, 1fr));
37
+ }
38
+ .lx-grid-lg-3 {
39
+ grid-template-columns: repeat(3, minmax(0, 1fr));
40
+ }
41
+ .lx-grid-lg-4 {
42
+ grid-template-columns: repeat(4, minmax(0, 1fr));
43
+ }
44
+ }
45
+ .lx-col-span-2 {
46
+ grid-column: span 2 / span 2;
47
+ }
48
+ .lx-col-span-3 {
49
+ grid-column: span 3 / span 3;
50
+ }
51
+ .lx-col-span-full {
52
+ grid-column: 1 / -1;
53
+ }
54
+
55
+ // ── Info tip ──
56
+ // Inline term help (InfoTip).
57
+ .infotip {
58
+ position: relative;
59
+ display: inline-flex;
60
+ vertical-align: middle;
61
+ margin-left: 5px;
62
+ }
63
+ .infotip-btn {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ width: 16px;
68
+ height: 16px;
69
+ padding: 0;
70
+ border: none;
71
+ border-radius: 999px;
72
+ background: transparent;
73
+ color: var(--color-text-subtle);
74
+ cursor: help;
75
+ transform: scaleY(-1);
76
+ transition: color 0.15s;
77
+ }
78
+ .infotip-btn:hover,
79
+ .infotip-btn:focus-visible {
80
+ color: var(--color-primary);
81
+ outline: none;
82
+ }
83
+ .infotip-pop {
84
+ position: absolute;
85
+ bottom: calc(100% + 8px);
86
+ left: 50%;
87
+ transform: translateX(-50%);
88
+ z-index: 220;
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: 4px;
92
+ width: 280px;
93
+ max-width: 78vw;
94
+ padding: 12px 14px;
95
+ background: var(--color-surface);
96
+ border: none;
97
+ border-radius: var(--radius-lg);
98
+ box-shadow: var(--shadow-overlay);
99
+ text-align: left;
100
+ white-space: normal;
101
+ cursor: default;
102
+ }
103
+ .infotip-pop::after {
104
+ content: "";
105
+ position: absolute;
106
+ top: 100%;
107
+ left: 50%;
108
+ transform: translateX(-50%);
109
+ border: 6px solid transparent;
110
+ border-top-color: var(--color-surface);
111
+ }
112
+ // Portalled to <body>: position is set inline (fixed top/left), so drop the
113
+ // anchored offsets and the centering transform, and hide the arrow. z-index
114
+ // sits above the modal backdrop so it shows over the modal, not behind it.
115
+ .infotip-pop.is-portal {
116
+ bottom: auto;
117
+ transform: none;
118
+ z-index: 1600;
119
+ }
120
+ .infotip-pop.is-portal::after {
121
+ display: none;
122
+ }
123
+ .infotip-term {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 8px;
127
+ font-size: 13px;
128
+ font-weight: 600;
129
+ color: var(--color-text);
130
+ }
131
+ .infotip-full {
132
+ font-size: 12px;
133
+ font-weight: 500;
134
+ color: var(--color-text);
135
+ }
136
+ .infotip-def {
137
+ font-size: 12px;
138
+ color: var(--color-text-muted);
139
+ line-height: 1.5;
140
+ }
141
+
142
+ // ── Page header ──
143
+ .page-header {
144
+ margin-bottom: 24px;
145
+ }
146
+ .page-title {
147
+ font-size: 24px;
148
+ font-weight: 600;
149
+ letter-spacing: -0.02em;
150
+ color: var(--color-text);
151
+ }
152
+ .page-subtitle {
153
+ font-size: 14px;
154
+ font-weight: 400;
155
+ color: var(--color-text-muted);
156
+ margin-top: 2px;
157
+ }
158
+ .page-actions {
159
+ display: flex;
160
+ gap: 8px;
161
+ align-items: center;
162
+ }
163
+
164
+ // Tablet and below: the masthead stops being a single row — the title block and
165
+ // its actions stack, and the actions stretch to the full width rather than
166
+ // crowding the title. Bootstrap's lg tier (991.98px), not an invented breakpoint.
167
+ //
168
+ // Selectors are DOUBLED (`.page-header.page-header`) on purpose. A media query
169
+ // adds no specificity, so a consumer's own single-class rule — written later in
170
+ // the cascade, as a consuming app's stylesheet always is — silently beats the
171
+ // responsive rule and the layout never collapses. Doubling puts these at (0,2,0)
172
+ // so they hold, while still leaving a consumer free to override deliberately.
173
+ @media (max-width: 991.98px) {
174
+ .page-header.page-header {
175
+ flex-direction: column;
176
+ align-items: stretch;
177
+ gap: 12px;
178
+ }
179
+ .page-actions.page-actions {
180
+ flex-wrap: wrap;
181
+ width: 100%;
182
+ }
183
+ // A two-up field row is too tight for a tablet column.
184
+ .field-row.field-row,
185
+ .field-row-3.field-row-3 {
186
+ grid-template-columns: 1fr;
187
+ }
188
+ // A fixed 420px drawer leaves too little of the page visible on a tablet.
189
+ .drawer.drawer {
190
+ width: 100vw;
191
+ max-width: 420px;
192
+ }
193
+ }
194
+
195
+ // ── Row-action / icon buttons ──
196
+ .btn-icon {
197
+ min-height: 0;
198
+ padding: 8px;
199
+ border-radius: 999px;
200
+ corner-shape: round;
201
+ }
202
+
203
+ // Circular icon buttons in the top bar (minimal dashboard chrome).
204
+ .topbar-icon-btn {
205
+ position: relative;
206
+ display: inline-flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ width: 36px;
210
+ height: 36px;
211
+ flex: 0 0 auto;
212
+ border: none;
213
+ border-radius: 999px;
214
+ background: transparent;
215
+ color: var(--color-text-muted);
216
+ cursor: pointer;
217
+ transition:
218
+ background 0.15s ease,
219
+ color 0.15s ease;
220
+ }
221
+ .topbar-icon-btn:hover {
222
+ background: var(--color-primary-soft);
223
+ color: var(--color-primary);
224
+ }
225
+ .topbar-icon-btn.has-badge::after {
226
+ content: "";
227
+ position: absolute;
228
+ top: 9px;
229
+ right: 9px;
230
+ width: 8px;
231
+ height: 8px;
232
+ border-radius: 999px;
233
+ background: var(--color-danger);
234
+ border: 2px solid var(--color-bg);
235
+ }
236
+
237
+ // ── Spinner / shimmer ──
238
+ .lx-spinner {
239
+ width: 18px;
240
+ height: 18px;
241
+ border: 2.5px solid var(--color-border);
242
+ border-top-color: var(--color-primary);
243
+ border-radius: 999px;
244
+ animation: lx-spin 0.7s linear infinite;
245
+ display: inline-block;
246
+ flex-shrink: 0;
247
+ }
248
+ @keyframes lx-spin {
249
+ to {
250
+ transform: rotate(360deg);
251
+ }
252
+ }
253
+ // Small (14px) spinner variant.
254
+ .lx-spinner-sm {
255
+ width: 14px;
256
+ height: 14px;
257
+ border-width: 2px;
258
+ }
259
+ // Spinner for the Button loading state.
260
+ .lx-spin {
261
+ animation: lx-spin 0.8s linear infinite;
262
+ }
263
+
264
+ // Skeleton shimmer — used ONLY while loading the first data (loading &&
265
+ // empty), to tell "loading first data" apart from "loaded but empty".
266
+ .skeleton {
267
+ position: relative;
268
+ overflow: hidden;
269
+ display: block;
270
+ background: var(--color-bg-muted);
271
+ border-radius: var(--radius-sm);
272
+ }
273
+ .skeleton::after {
274
+ content: "";
275
+ position: absolute;
276
+ inset: 0;
277
+ transform: translateX(-100%);
278
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
279
+ animation: skeleton-shimmer 1.4s ease-in-out infinite;
280
+ }
281
+ @keyframes skeleton-shimmer {
282
+ 100% {
283
+ transform: translateX(100%);
284
+ }
285
+ }
286
+ :root[data-theme="dark"] .skeleton::after {
287
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
288
+ }
289
+ @media (prefers-reduced-motion: reduce) {
290
+ .skeleton::after {
291
+ animation: none;
292
+ }
293
+ }
294
+
295
+ // Info callout — a soft branded aside for guidance, distinct from `.alert`.
296
+ // An alert reports a state; a callout explains something and stays.
297
+ .info-callout {
298
+ display: flex;
299
+ align-items: center;
300
+ gap: 12px;
301
+ padding: 14px 18px;
302
+ border: none;
303
+ // A single accent edge instead of a full border: it reads as an aside rather
304
+ // than a boxed message.
305
+ border-left: 2px solid color-mix(in srgb, var(--color-primary) 50%, transparent);
306
+ border-radius: var(--radius-xl);
307
+ corner-shape: squircle;
308
+ background: var(--color-primary-soft);
309
+ font-size: 14px;
310
+ font-weight: 500;
311
+ line-height: 1.5;
312
+ color: var(--color-primary);
313
+ }
314
+ .info-callout-icon {
315
+ flex: none;
316
+ display: inline-flex;
317
+ align-items: center;
318
+ color: var(--color-primary);
319
+ }
320
+ .info-callout strong {
321
+ font-weight: 700;
322
+ color: var(--color-primary);
323
+ }
324
+
325
+ // ============================================================================
326
+ // Inline help glyph — a hint attached to a word rather than to a field label.
327
+ // (For a field label use `.infotip`, which is a real popover button.)
328
+ // ============================================================================
329
+
330
+ .tip-icon {
331
+ display: inline-flex;
332
+ margin-left: 4px;
333
+ vertical-align: middle;
334
+ color: var(--color-text-subtle);
335
+ // `help` rather than `pointer`: the glyph explains, it does not navigate.
336
+ cursor: help;
337
+ }
338
+ .tip-icon:hover {
339
+ color: var(--color-primary);
340
+ }