@webority/theme 0.6.4 → 0.6.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/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_tokens.scss +4 -0
- package/scss/wui/_controls.scss +150 -0
- package/scss/wui/_data.scss +57 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "Webority shared design system — Bootstrap 5.3 SCSS overrides, --wui-* tokens, and shared component CSS. The single source of brand chrome for React portals and Razor sites.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
package/scss/_tokens.scss
CHANGED
|
@@ -70,6 +70,10 @@
|
|
|
70
70
|
// rows, so they must line up — they were 46.8px and 45px, which reads as a
|
|
71
71
|
// misalignment on every filter bar. 44px is also the WCAG touch-target floor.
|
|
72
72
|
--wui-control-height: 44px;
|
|
73
|
+
// Optional size steps (sm/lg) for dense toolbars / marketing-scale fields. md is the
|
|
74
|
+
// default above; a control opts into a step with .input-sm / .input-lg.
|
|
75
|
+
--wui-control-height-sm: 36px;
|
|
76
|
+
--wui-control-height-lg: 52px;
|
|
73
77
|
|
|
74
78
|
// Scrollbar thumb — one quiet grey-blue at three interaction strengths.
|
|
75
79
|
--wui-scrollbar: rgba(145, 158, 171, 0.45);
|
package/scss/wui/_controls.scss
CHANGED
|
@@ -193,6 +193,18 @@
|
|
|
193
193
|
box-shadow 0.12s;
|
|
194
194
|
font-family: inherit;
|
|
195
195
|
}
|
|
196
|
+
// Size steps — sm/lg opt out of the shared 44px control height for dense toolbars or
|
|
197
|
+
// marketing-scale fields. md is the base rule above; AppInput adds .input-sm / .input-lg.
|
|
198
|
+
.input-sm {
|
|
199
|
+
height: var(--wui-control-height-sm);
|
|
200
|
+
padding: 6px 10px;
|
|
201
|
+
font-size: 13px;
|
|
202
|
+
}
|
|
203
|
+
.input-lg {
|
|
204
|
+
height: var(--wui-control-height-lg);
|
|
205
|
+
padding: 14px 16px;
|
|
206
|
+
font-size: 16px;
|
|
207
|
+
}
|
|
196
208
|
.textarea {
|
|
197
209
|
height: auto;
|
|
198
210
|
min-height: 120px;
|
|
@@ -200,6 +212,13 @@
|
|
|
200
212
|
line-height: 1.5;
|
|
201
213
|
resize: vertical;
|
|
202
214
|
}
|
|
215
|
+
// Auto-grow — JS drives the height to fit the content (Razor: data-app-autosize wired in
|
|
216
|
+
// webority-ui.js; React: the autoGrow prop). The manual grabber and the scrollbar are off
|
|
217
|
+
// so they can't fight the measured height.
|
|
218
|
+
.textarea.autosize {
|
|
219
|
+
resize: none;
|
|
220
|
+
overflow: hidden;
|
|
221
|
+
}
|
|
203
222
|
.input:hover:not(:focus):not(:disabled),
|
|
204
223
|
.select:hover:not(:focus):not(:disabled),
|
|
205
224
|
.textarea:hover:not(:focus):not(:disabled) {
|
|
@@ -496,12 +515,143 @@ textarea.textarea:focus {
|
|
|
496
515
|
background: transparent;
|
|
497
516
|
}
|
|
498
517
|
|
|
518
|
+
// Clearable ✕ — a trailing reset button, placed inside .input-wrap / .input-icon. It only
|
|
519
|
+
// renders while the field has a value; .has-clear reserves the room so text never sits under it.
|
|
520
|
+
.input.has-clear {
|
|
521
|
+
padding-right: 40px;
|
|
522
|
+
}
|
|
523
|
+
.input-clear {
|
|
524
|
+
position: absolute;
|
|
525
|
+
right: 8px;
|
|
526
|
+
top: 50%;
|
|
527
|
+
transform: translateY(-50%);
|
|
528
|
+
display: grid;
|
|
529
|
+
place-items: center;
|
|
530
|
+
width: 28px;
|
|
531
|
+
height: 28px;
|
|
532
|
+
padding: 0;
|
|
533
|
+
border: none;
|
|
534
|
+
border-radius: 50%;
|
|
535
|
+
background: transparent;
|
|
536
|
+
color: var(--color-text-subtle);
|
|
537
|
+
cursor: pointer;
|
|
538
|
+
transition:
|
|
539
|
+
color 0.12s ease,
|
|
540
|
+
background 0.12s ease;
|
|
541
|
+
animation: wui-clear-in 0.12s var(--wui-ease-ui);
|
|
542
|
+
}
|
|
543
|
+
.input-clear:hover {
|
|
544
|
+
color: var(--color-text);
|
|
545
|
+
background: var(--wui-bg-muted);
|
|
546
|
+
}
|
|
547
|
+
@keyframes wui-clear-in {
|
|
548
|
+
from { opacity: 0; transform: translateY(-50%) scale(0.72); }
|
|
549
|
+
to { opacity: 1; transform: translateY(-50%) scale(1); }
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Prefix / suffix addon — a bordered box before/after the input (₹, https://, .com). The
|
|
553
|
+
// WRAPPER owns the border + radius so the affixes and input read as one control; the input
|
|
554
|
+
// itself goes borderless. Focus/hover/error move to the wrapper.
|
|
555
|
+
.input-affixed {
|
|
556
|
+
display: flex;
|
|
557
|
+
align-items: stretch;
|
|
558
|
+
width: 100%;
|
|
559
|
+
border: 1px solid var(--color-border);
|
|
560
|
+
border-radius: var(--radius);
|
|
561
|
+
background: var(--color-surface);
|
|
562
|
+
transition: border-color 0.12s ease;
|
|
563
|
+
}
|
|
564
|
+
.input-affixed:hover:not(:focus-within) {
|
|
565
|
+
border-color: var(--color-border-strong);
|
|
566
|
+
}
|
|
567
|
+
.input-affixed:focus-within {
|
|
568
|
+
border-color: var(--color-primary);
|
|
569
|
+
}
|
|
570
|
+
.input-affixed:has(.input.error) {
|
|
571
|
+
border-color: var(--color-danger);
|
|
572
|
+
}
|
|
573
|
+
.input-affixed .input {
|
|
574
|
+
flex: 1;
|
|
575
|
+
min-width: 0;
|
|
576
|
+
border: 0;
|
|
577
|
+
border-radius: 0;
|
|
578
|
+
background: transparent;
|
|
579
|
+
}
|
|
580
|
+
.input-affixed .affix {
|
|
581
|
+
display: flex;
|
|
582
|
+
align-items: center;
|
|
583
|
+
padding: 0 12px;
|
|
584
|
+
background: var(--wui-bg-muted);
|
|
585
|
+
color: var(--color-text-subtle);
|
|
586
|
+
font-size: 14px;
|
|
587
|
+
white-space: nowrap;
|
|
588
|
+
}
|
|
589
|
+
.input-affixed .affix-pre {
|
|
590
|
+
border-right: 1px solid var(--color-border);
|
|
591
|
+
border-radius: var(--radius) 0 0 var(--radius);
|
|
592
|
+
}
|
|
593
|
+
.input-affixed .affix-suf {
|
|
594
|
+
border-left: 1px solid var(--color-border);
|
|
595
|
+
border-radius: 0 var(--radius) var(--radius) 0;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Floating label — the label starts centred as the placeholder, then slides up and shrinks
|
|
599
|
+
// on focus or when the field is filled. Opt-in (floatingLabel); the input carries a single
|
|
600
|
+
// space as its placeholder so :placeholder-shown drives the "empty" state. Standalone shell.
|
|
601
|
+
.field-float {
|
|
602
|
+
position: relative;
|
|
603
|
+
width: 100%;
|
|
604
|
+
}
|
|
605
|
+
.field-float .float-label {
|
|
606
|
+
position: absolute;
|
|
607
|
+
left: 12px;
|
|
608
|
+
top: 50%;
|
|
609
|
+
transform: translateY(-50%);
|
|
610
|
+
transform-origin: left center;
|
|
611
|
+
margin: 0;
|
|
612
|
+
padding: 0 4px;
|
|
613
|
+
max-width: calc(100% - 24px);
|
|
614
|
+
overflow: hidden;
|
|
615
|
+
text-overflow: ellipsis;
|
|
616
|
+
white-space: nowrap;
|
|
617
|
+
font-size: 14px;
|
|
618
|
+
color: var(--color-text-subtle);
|
|
619
|
+
background: var(--color-surface);
|
|
620
|
+
pointer-events: none;
|
|
621
|
+
transition:
|
|
622
|
+
top 0.15s var(--wui-ease-ui),
|
|
623
|
+
transform 0.15s var(--wui-ease-ui),
|
|
624
|
+
color 0.15s var(--wui-ease-ui);
|
|
625
|
+
}
|
|
626
|
+
.field-float .input:focus ~ .float-label,
|
|
627
|
+
.field-float .input:not(:placeholder-shown) ~ .float-label {
|
|
628
|
+
top: 0;
|
|
629
|
+
transform: translateY(-50%) scale(0.86);
|
|
630
|
+
}
|
|
631
|
+
.field-float .input:focus ~ .float-label {
|
|
632
|
+
color: var(--color-primary);
|
|
633
|
+
}
|
|
634
|
+
.field-float .input.error ~ .float-label {
|
|
635
|
+
color: var(--color-danger-text);
|
|
636
|
+
}
|
|
637
|
+
|
|
499
638
|
.hint,
|
|
500
639
|
.field-hint {
|
|
501
640
|
margin-top: 2px;
|
|
502
641
|
font-size: 12px;
|
|
503
642
|
color: var(--color-text-subtle);
|
|
504
643
|
}
|
|
644
|
+
// Character counter — a right-aligned count under the field; turns danger at the limit.
|
|
645
|
+
.field-count {
|
|
646
|
+
margin-top: 2px;
|
|
647
|
+
font-size: 12px;
|
|
648
|
+
line-height: normal;
|
|
649
|
+
text-align: right;
|
|
650
|
+
color: var(--color-text-subtle);
|
|
651
|
+
}
|
|
652
|
+
.field-count.at-limit {
|
|
653
|
+
color: var(--color-danger-text);
|
|
654
|
+
}
|
|
505
655
|
.label {
|
|
506
656
|
display: block;
|
|
507
657
|
font-size: 14px;
|
package/scss/wui/_data.scss
CHANGED
|
@@ -261,6 +261,7 @@
|
|
|
261
261
|
gap: 2px;
|
|
262
262
|
}
|
|
263
263
|
.table-ledger .u-name {
|
|
264
|
+
min-width: 0;
|
|
264
265
|
font-size: 14px;
|
|
265
266
|
font-weight: 500;
|
|
266
267
|
line-height: 19px;
|
|
@@ -273,6 +274,28 @@
|
|
|
273
274
|
margin-bottom: 2px;
|
|
274
275
|
}
|
|
275
276
|
}
|
|
277
|
+
// Clamp a ledger name to one line. OPT-IN — see below for why it is not the default.
|
|
278
|
+
//
|
|
279
|
+
// In a ledger a long name is the normal case: a 112-char project name wrapped to three lines
|
|
280
|
+
// and took its row to 99px against 61px for every other row, breaking the row rhythm down the
|
|
281
|
+
// whole table.
|
|
282
|
+
//
|
|
283
|
+
// `white-space: nowrap` + ellipsis is the WRONG fix and was measured to be: under the
|
|
284
|
+
// ledger's auto table layout a nowrap cell demands its full unwrapped width, which widened a
|
|
285
|
+
// 360px column to 854px and pushed the table into horizontal scroll. It trades row height for
|
|
286
|
+
// column geometry. A line clamp leaves the text wrappable, so min-content stays one word.
|
|
287
|
+
//
|
|
288
|
+
// Opt-in rather than default because the library styles `.u-name` but never emits it — it is
|
|
289
|
+
// applied by consumers, and `-webkit-box` stacks element children vertically. A product that
|
|
290
|
+
// composes a name with an inline badge would get the badge under the name. Flip this to the
|
|
291
|
+
// default once the composition is known to be text-only everywhere.
|
|
292
|
+
.u-name-clamp {
|
|
293
|
+
display: -webkit-box;
|
|
294
|
+
-webkit-line-clamp: 1;
|
|
295
|
+
-webkit-box-orient: vertical;
|
|
296
|
+
overflow: hidden;
|
|
297
|
+
}
|
|
298
|
+
|
|
276
299
|
// An identity-cell name is often a link, so it inherits Bootstrap's
|
|
277
300
|
// underline-on-hover. The colour shift is the affordance here.
|
|
278
301
|
.table-ledger a.u-name,
|
|
@@ -401,6 +424,23 @@
|
|
|
401
424
|
border-radius: 999px;
|
|
402
425
|
}
|
|
403
426
|
|
|
427
|
+
// The identity/name column — a STABLE hook, because position is not one.
|
|
428
|
+
//
|
|
429
|
+
// The library owns .lx-select-cell, so a ledger's first cell is the 44px checkbox when
|
|
430
|
+
// selection is on and the name when it is off. `:first-child` therefore changes meaning
|
|
431
|
+
// with a permission flag: a consumer's 360px name-column floor landed on the 44px checkbox
|
|
432
|
+
// for any user who could select rows, inflating that column 8x and pushing the table into
|
|
433
|
+
// horizontal scroll — visible only to users holding that one permission, which is why it
|
|
434
|
+
// survived review. Sizing the right column should not require guessing at position.
|
|
435
|
+
//
|
|
436
|
+
// min-width:0 is the rule rather than a placeholder: a table cell defaults to min-width:auto,
|
|
437
|
+
// which refuses to shrink below its content and is what makes truncation impossible in the
|
|
438
|
+
// first place.
|
|
439
|
+
.table-ledger th.lx-name-cell,
|
|
440
|
+
.table-ledger td.lx-name-cell {
|
|
441
|
+
min-width: 0;
|
|
442
|
+
}
|
|
443
|
+
|
|
404
444
|
// Row-selection checkbox column.
|
|
405
445
|
.table-ledger th.lx-select-cell,
|
|
406
446
|
.table-ledger td.lx-select-cell {
|
|
@@ -572,17 +612,30 @@
|
|
|
572
612
|
// Table extras — a sortable header and numeric cells. These sit AFTER the
|
|
573
613
|
// .table-ledger rules above on purpose: they are opt-in additions on top of
|
|
574
614
|
// whichever table variant is in use, not part of the base a variant overrides.
|
|
615
|
+
// NEVER give a <th> a display that is not a table display.
|
|
616
|
+
//
|
|
617
|
+
// This carried `display: flex`, which takes the cell OUT of the table's layout entirely: it
|
|
618
|
+
// stops being a column, so every sortable header left the header row and stacked at its own
|
|
619
|
+
// y-position. Measured on a consumer's page, a sortable table's headers sat at three
|
|
620
|
+
// different y-values against the neighbouring non-sortable table's one, and the header box
|
|
621
|
+
// grew from 42px to 126px. It shipped in 0.6.4.
|
|
622
|
+
//
|
|
623
|
+
// Nothing else was wrong — background, colour, padding, borders, font, tracking, radii all
|
|
624
|
+
// resolved correctly. One declaration disintegrated the row, and only for tables that
|
|
625
|
+
// actually have a sortable column, which is why half a product's screens looked fine.
|
|
626
|
+
//
|
|
627
|
+
// The alignment it was buying is done on the icon instead, which is an inline box inside a
|
|
628
|
+
// normal cell and cannot disturb the table.
|
|
575
629
|
.table th.sortable {
|
|
576
|
-
display: flex;
|
|
577
|
-
align-items: center;
|
|
578
|
-
gap: 6px;
|
|
579
630
|
cursor: pointer;
|
|
580
631
|
user-select: none;
|
|
581
632
|
}
|
|
582
633
|
.table th.sortable .sort-icon {
|
|
583
634
|
// Reserved space so the header does not jump width when the arrow appears.
|
|
635
|
+
display: inline-block;
|
|
636
|
+
vertical-align: middle;
|
|
584
637
|
width: 12px;
|
|
585
|
-
|
|
638
|
+
margin-left: 6px;
|
|
586
639
|
color: var(--color-text-subtle);
|
|
587
640
|
opacity: 0;
|
|
588
641
|
transition: opacity 0.12s ease;
|