@webority/theme 0.17.3 → 0.18.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/dist/purge-safelist.json +15 -0
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/wui/_card.scss +1 -1
- package/scss/wui/_controls.scss +26 -0
- package/scss/wui/_data.scss +168 -0
- package/scss/wui/_flow.scss +74 -1
- package/scss/wui/_typography.scss +2 -14
- package/scss/wui/_utilities.scss +26 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
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/wui/_card.scss
CHANGED
package/scss/wui/_controls.scss
CHANGED
|
@@ -74,6 +74,32 @@
|
|
|
74
74
|
grid-column: 2;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// AppRadioGroup: options in a column, or a wrapping row with `inline`.
|
|
78
|
+
.wui-radio-group {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 10px;
|
|
82
|
+
}
|
|
83
|
+
.wui-radio-group-inline {
|
|
84
|
+
flex-direction: row;
|
|
85
|
+
flex-wrap: wrap;
|
|
86
|
+
column-gap: 24px;
|
|
87
|
+
}
|
|
88
|
+
.wui-radio-group > .form-check {
|
|
89
|
+
margin-bottom: 0;
|
|
90
|
+
}
|
|
91
|
+
// A lone .form-check lets its label shrink to nothing so long consent text can wrap; in a row
|
|
92
|
+
// of options that broke words mid-way ("Mont hly"). Keep each option's words whole and let the
|
|
93
|
+
// row wrap instead.
|
|
94
|
+
.wui-radio-group .form-check-label {
|
|
95
|
+
min-width: auto;
|
|
96
|
+
}
|
|
97
|
+
// Beside-label forms centre a label on a text control; a radio group's first option is shorter,
|
|
98
|
+
// so its label lines up with that row instead.
|
|
99
|
+
.wui-form-labels-start .field:has(> .wui-radio-group) > .label {
|
|
100
|
+
padding-top: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
77
103
|
// Glyph — shared by BS form-check-input and the wui-check alias.
|
|
78
104
|
.form-check-input,
|
|
79
105
|
.wui-check,
|
package/scss/wui/_data.scss
CHANGED
|
@@ -470,6 +470,14 @@
|
|
|
470
470
|
margin-left: auto;
|
|
471
471
|
gap: 8px;
|
|
472
472
|
}
|
|
473
|
+
// A filter control wider than a phone (a four-option segmented strip) scrolls inside the toolbar
|
|
474
|
+
// instead of widening the page.
|
|
475
|
+
.wui-table-toolbar-filters {
|
|
476
|
+
min-width: 0;
|
|
477
|
+
max-width: 100%;
|
|
478
|
+
overflow-x: auto;
|
|
479
|
+
scrollbar-width: none;
|
|
480
|
+
}
|
|
473
481
|
// The search box is full-width on its own; in a toolbar it is one control among several.
|
|
474
482
|
.wui-table-toolbar .search-wrap {
|
|
475
483
|
width: min(320px, 100%);
|
|
@@ -722,6 +730,166 @@
|
|
|
722
730
|
inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg)));
|
|
723
731
|
}
|
|
724
732
|
|
|
733
|
+
// Columns menu (AppColumnMenu / <app-column-menu>) and the column it hides on Razor.
|
|
734
|
+
.table-ledger .wui-col-hidden {
|
|
735
|
+
display: none;
|
|
736
|
+
}
|
|
737
|
+
.wui-column-menu {
|
|
738
|
+
min-width: 200px;
|
|
739
|
+
padding: 8px 0;
|
|
740
|
+
}
|
|
741
|
+
.wui-column-menu-title {
|
|
742
|
+
padding: 4px 14px 8px;
|
|
743
|
+
font-size: 12px;
|
|
744
|
+
font-weight: 600;
|
|
745
|
+
letter-spacing: 0.04em;
|
|
746
|
+
text-transform: uppercase;
|
|
747
|
+
color: var(--color-text-subtle);
|
|
748
|
+
}
|
|
749
|
+
.wui-column-menu-item {
|
|
750
|
+
display: flex;
|
|
751
|
+
align-items: center;
|
|
752
|
+
gap: 10px;
|
|
753
|
+
padding: 6px 14px;
|
|
754
|
+
font-size: 14px;
|
|
755
|
+
color: var(--color-text);
|
|
756
|
+
cursor: pointer;
|
|
757
|
+
}
|
|
758
|
+
.wui-column-menu-item:hover {
|
|
759
|
+
background: var(--color-bg-subtle);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// A row that opens its record (AppDataTable rowHref / onRowClick, Razor data-href).
|
|
763
|
+
.table-ledger tbody tr.wui-row-link {
|
|
764
|
+
cursor: pointer;
|
|
765
|
+
}
|
|
766
|
+
.table-ledger tbody tr.wui-row-link:focus-visible {
|
|
767
|
+
outline: 2px solid var(--color-primary);
|
|
768
|
+
outline-offset: -2px;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Stacked on phones (AppDataTable `stackOnMobile`, Razor `stack-on-mobile`): below sm each row is a
|
|
772
|
+
// card. The first column (after the library's select/expand cells) leads the card at full width,
|
|
773
|
+
// every other cell is a "label: value" line from its data-label, the select checkbox sits in the
|
|
774
|
+
// card's top corner and the row actions close the card. The header row is visually hidden, not
|
|
775
|
+
// removed, so the column names stay available to assistive technology.
|
|
776
|
+
@include media-breakpoint-down(sm) {
|
|
777
|
+
// Cards flow with the page: no sideways scroll, and no maxHeight scroll box (an inline
|
|
778
|
+
// max-height from `maxHeight` has to be overridden, hence !important).
|
|
779
|
+
.table-wrap:has(> .table-ledger.wui-table-stack) {
|
|
780
|
+
overflow: visible;
|
|
781
|
+
max-height: none !important;
|
|
782
|
+
}
|
|
783
|
+
.table-wrap:has(> .table-ledger.wui-table-stack) .table-ledger thead th {
|
|
784
|
+
position: static;
|
|
785
|
+
}
|
|
786
|
+
.table-ledger.wui-table-stack,
|
|
787
|
+
.table-ledger.wui-table-stack > tbody,
|
|
788
|
+
.table-ledger.wui-table-stack > tfoot,
|
|
789
|
+
.table-ledger.wui-table-stack > tbody > tr,
|
|
790
|
+
.table-ledger.wui-table-stack > tfoot > tr {
|
|
791
|
+
display: block;
|
|
792
|
+
width: 100%;
|
|
793
|
+
}
|
|
794
|
+
.table-ledger.wui-table-stack > thead {
|
|
795
|
+
// Block, so overflow:hidden applies; a table-header-group ignores it and its row still
|
|
796
|
+
// widened the page.
|
|
797
|
+
display: block;
|
|
798
|
+
position: absolute;
|
|
799
|
+
width: 1px;
|
|
800
|
+
height: 1px;
|
|
801
|
+
overflow: hidden;
|
|
802
|
+
clip-path: inset(50%);
|
|
803
|
+
white-space: nowrap;
|
|
804
|
+
}
|
|
805
|
+
.table-ledger.wui-table-stack > tbody > tr,
|
|
806
|
+
.table-ledger.wui-table-stack > tfoot > tr {
|
|
807
|
+
position: relative;
|
|
808
|
+
margin-bottom: 12px;
|
|
809
|
+
padding: 12px 16px;
|
|
810
|
+
border: 1px solid var(--color-border);
|
|
811
|
+
border-radius: var(--radius-lg, 12px);
|
|
812
|
+
}
|
|
813
|
+
.table-ledger.wui-table-stack > tbody > tr > td,
|
|
814
|
+
.table-ledger.wui-table-stack > tfoot > tr > td {
|
|
815
|
+
display: flex;
|
|
816
|
+
align-items: center;
|
|
817
|
+
justify-content: space-between;
|
|
818
|
+
gap: 12px;
|
|
819
|
+
height: auto;
|
|
820
|
+
min-height: 0;
|
|
821
|
+
padding: 6px 0;
|
|
822
|
+
border: 0;
|
|
823
|
+
text-align: right;
|
|
824
|
+
// A pinned column has nothing to pin to in a card. The pinned-column selectors outrank this
|
|
825
|
+
// one by design (they must beat every table rule), so the card layout overrides them here.
|
|
826
|
+
position: static !important;
|
|
827
|
+
}
|
|
828
|
+
.table-ledger.wui-table-stack > tbody > tr > td::before,
|
|
829
|
+
.table-ledger.wui-table-stack > tfoot > tr > td::before {
|
|
830
|
+
content: attr(data-label);
|
|
831
|
+
flex: none;
|
|
832
|
+
font-size: 12px;
|
|
833
|
+
font-weight: 500;
|
|
834
|
+
color: var(--color-text-subtle);
|
|
835
|
+
text-align: left;
|
|
836
|
+
}
|
|
837
|
+
// A value takes the space beside its label and wraps inside the card rather than running past
|
|
838
|
+
// its edge (a long email under an avatar did).
|
|
839
|
+
.table-ledger.wui-table-stack > * > tr > td > * {
|
|
840
|
+
min-width: 0;
|
|
841
|
+
overflow-wrap: anywhere;
|
|
842
|
+
}
|
|
843
|
+
.table-ledger.wui-table-stack > tbody > tr > td .u-meta {
|
|
844
|
+
align-items: flex-end;
|
|
845
|
+
}
|
|
846
|
+
// The leading cell is the card's title line: full width, no label.
|
|
847
|
+
.table-ledger.wui-table-stack > * > tr > td:not(.wui-select-cell, .wui-expand-cell):is(:first-child, .wui-select-cell + td, .wui-expand-cell + td) {
|
|
848
|
+
justify-content: flex-start;
|
|
849
|
+
padding: 0 32px 6px 0;
|
|
850
|
+
text-align: left;
|
|
851
|
+
}
|
|
852
|
+
.table-ledger.wui-table-stack > * > tr > td:not(.wui-select-cell, .wui-expand-cell):is(:first-child, .wui-select-cell + td, .wui-expand-cell + td) .u-meta {
|
|
853
|
+
align-items: flex-start;
|
|
854
|
+
}
|
|
855
|
+
.table-ledger.wui-table-stack > * > tr > td:not(.wui-select-cell, .wui-expand-cell):is(:first-child, .wui-select-cell + td, .wui-expand-cell + td)::before {
|
|
856
|
+
content: none;
|
|
857
|
+
}
|
|
858
|
+
// Empty cells (a footer's blank columns) take no line.
|
|
859
|
+
.table-ledger.wui-table-stack > * > tr > td:empty {
|
|
860
|
+
display: none;
|
|
861
|
+
}
|
|
862
|
+
.table-ledger.wui-table-stack > tbody > tr > td.wui-select-cell,
|
|
863
|
+
.table-ledger.wui-table-stack > tbody > tr > td.wui-expand-cell {
|
|
864
|
+
// !important for the same reason as the static rule above: a pinned-column `left` with this
|
|
865
|
+
// `right` would stretch the cell across the card.
|
|
866
|
+
position: absolute !important;
|
|
867
|
+
top: 12px;
|
|
868
|
+
left: auto !important;
|
|
869
|
+
right: 12px;
|
|
870
|
+
width: auto;
|
|
871
|
+
min-width: 0;
|
|
872
|
+
max-width: none;
|
|
873
|
+
padding: 0;
|
|
874
|
+
}
|
|
875
|
+
.table-ledger.wui-table-stack > tbody > tr > td:has(> .u-row-actions) {
|
|
876
|
+
justify-content: flex-end;
|
|
877
|
+
margin-top: 6px;
|
|
878
|
+
padding-top: 10px;
|
|
879
|
+
border-top: 1px solid var(--color-border);
|
|
880
|
+
}
|
|
881
|
+
.table-ledger.wui-table-stack > tbody > tr > td:has(> .u-row-actions)::before {
|
|
882
|
+
content: none;
|
|
883
|
+
}
|
|
884
|
+
// The status bar becomes the card's left edge.
|
|
885
|
+
.table-ledger.wui-table-stack > tbody > tr[data-status] {
|
|
886
|
+
border-left: 3px solid var(--wui-row-status);
|
|
887
|
+
}
|
|
888
|
+
.table-ledger.wui-table-stack > tbody > tr[data-status] > td:first-child {
|
|
889
|
+
box-shadow: none;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
725
893
|
// Totals row (AppDataTable column `footer`, or an author <tfoot> on Razor).
|
|
726
894
|
.table-ledger tfoot td {
|
|
727
895
|
height: 48px;
|
package/scss/wui/_flow.scss
CHANGED
|
@@ -780,9 +780,20 @@ button.step:focus-visible .step-circle {
|
|
|
780
780
|
flex-direction: column;
|
|
781
781
|
gap: 6px;
|
|
782
782
|
}
|
|
783
|
+
// Consecutive sections are separated by space and a light rule, and the action bar keeps its
|
|
784
|
+
// distance from the last field, so a long form never reads as one run of fields.
|
|
785
|
+
.form-section + .form-section {
|
|
786
|
+
margin-top: 32px;
|
|
787
|
+
padding-top: 32px;
|
|
788
|
+
border-top: 1px solid var(--color-border);
|
|
789
|
+
}
|
|
790
|
+
.form-section + .form-actions {
|
|
791
|
+
margin-top: 32px;
|
|
792
|
+
}
|
|
793
|
+
// Ranks below the card title (16px) a section usually sits in, and above the 14px field labels.
|
|
783
794
|
.form-section-title {
|
|
784
795
|
margin: 0;
|
|
785
|
-
font-size:
|
|
796
|
+
font-size: 15px;
|
|
786
797
|
font-weight: 600;
|
|
787
798
|
line-height: 1.3;
|
|
788
799
|
color: var(--color-text);
|
|
@@ -799,12 +810,74 @@ button.step:focus-visible .step-circle {
|
|
|
799
810
|
gap: 16px;
|
|
800
811
|
}
|
|
801
812
|
|
|
813
|
+
// layout="side": the title and description take a column beside the fields. Below lg the
|
|
814
|
+
// section stacks as usual, since a 240px column would starve the fields.
|
|
815
|
+
@include media-breakpoint-up(lg) {
|
|
816
|
+
.wui-form-side {
|
|
817
|
+
display: grid;
|
|
818
|
+
grid-template-columns: minmax(0, 240px) minmax(0, 1fr);
|
|
819
|
+
column-gap: 32px;
|
|
820
|
+
align-items: start;
|
|
821
|
+
}
|
|
822
|
+
.wui-form-side > .form-section-body {
|
|
823
|
+
grid-column: 2;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// labels="start": each label sits beside its control, one field per row. The label column is
|
|
828
|
+
// narrower when the section title already takes a column of its own. Below md labels return
|
|
829
|
+
// to the top.
|
|
830
|
+
.wui-form-labels-start {
|
|
831
|
+
--wui-form-label-width: 180px;
|
|
832
|
+
}
|
|
833
|
+
.wui-form-side.wui-form-labels-start {
|
|
834
|
+
--wui-form-label-width: 150px;
|
|
835
|
+
}
|
|
836
|
+
@include media-breakpoint-up(md) {
|
|
837
|
+
// Two classes outrank the row's own column count.
|
|
838
|
+
.wui-form-labels-start .form-row-grid {
|
|
839
|
+
grid-template-columns: minmax(0, 1fr);
|
|
840
|
+
}
|
|
841
|
+
.wui-form-labels-start .field {
|
|
842
|
+
display: grid;
|
|
843
|
+
grid-template-columns: var(--wui-form-label-width) minmax(0, 1fr);
|
|
844
|
+
column-gap: 20px;
|
|
845
|
+
row-gap: 4px;
|
|
846
|
+
align-items: start;
|
|
847
|
+
}
|
|
848
|
+
// Centres the label's first line on the control beside it, at any control height.
|
|
849
|
+
.wui-form-labels-start .field > .label {
|
|
850
|
+
grid-column: 1;
|
|
851
|
+
padding-top: calc((var(--wui-control-height) - 1.2em) / 2);
|
|
852
|
+
}
|
|
853
|
+
.wui-form-labels-start .field > :not(.label) {
|
|
854
|
+
grid-column: 2;
|
|
855
|
+
}
|
|
856
|
+
// A checkbox or switch has its label after the box; line the box up with the controls.
|
|
857
|
+
.wui-form-labels-start .form-row-grid > .form-check {
|
|
858
|
+
margin-left: calc(var(--wui-form-label-width) + 20px);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// density="compact": small-tier controls and tighter gaps, for admin screens and dialogs.
|
|
863
|
+
.wui-form-compact {
|
|
864
|
+
--wui-control-height: var(--wui-control-height-sm);
|
|
865
|
+
gap: 12px;
|
|
866
|
+
}
|
|
867
|
+
.wui-form-compact .form-section-body,
|
|
868
|
+
.wui-form-compact .form-row-grid {
|
|
869
|
+
gap: 12px;
|
|
870
|
+
}
|
|
871
|
+
|
|
802
872
|
// ============================================================================
|
|
803
873
|
.form-row-grid {
|
|
804
874
|
display: grid;
|
|
805
875
|
grid-template-columns: repeat(var(--form-cols, 2), minmax(0, 1fr));
|
|
806
876
|
gap: 16px;
|
|
807
877
|
align-items: start;
|
|
878
|
+
// In a form row every field fills its column, so a date picker drops its compact standalone
|
|
879
|
+
// width here and lines up with the fields beside it. A .date-w-* class on the field still wins.
|
|
880
|
+
--wui-datepicker-width: 100%;
|
|
808
881
|
}
|
|
809
882
|
// Below md, collapse to 1 column.
|
|
810
883
|
@include media-breakpoint-down(md) {
|
|
@@ -441,20 +441,8 @@ wui-multiselect[collapse-chips] .ms-chips {
|
|
|
441
441
|
.ms-input:focus {
|
|
442
442
|
outline: none;
|
|
443
443
|
}
|
|
444
|
-
// AppTagInput
|
|
445
|
-
//
|
|
446
|
-
// chips-first layout, so these overrides are scoped to `wui-taginput`.
|
|
447
|
-
wui-taginput .ms-input {
|
|
448
|
-
// Don't grow to fill — hold a modest resting width on the left; the chips take
|
|
449
|
-
// everything to the right. (align-items:center on the control centres the input
|
|
450
|
-
// against the chip row, so it needs no min-height of its own.)
|
|
451
|
-
flex: 0 1 auto;
|
|
452
|
-
flex-basis: 120px;
|
|
453
|
-
min-width: 96px;
|
|
454
|
-
}
|
|
455
|
-
wui-taginput .ms-chips {
|
|
456
|
-
flex: 1 1 auto;
|
|
457
|
-
}
|
|
444
|
+
// AppTagInput renders its input inside the chip row, after the chips, so the shared
|
|
445
|
+
// `.ms-chips > .ms-input` rule above lets it wrap with them and fill the rest of the line.
|
|
458
446
|
// Match AppInput's per-size heights: the shared `.search-select-multi` floors md at
|
|
459
447
|
// 40px, but AppInput's md is the 44px control-height token — raise the floor so the
|
|
460
448
|
// sm/md/lg progression lines up with a text field beside it. (sm 36 / lg 52 already
|
package/scss/wui/_utilities.scss
CHANGED
|
@@ -765,6 +765,32 @@
|
|
|
765
765
|
--alert-text: var(--color-danger-text-on-soft);
|
|
766
766
|
}
|
|
767
767
|
|
|
768
|
+
// AppErrorSummary: a danger alert holding a heading and one link per field error. It takes
|
|
769
|
+
// focus when it appears, so its focus ring is the alert's own edge rather than an outline.
|
|
770
|
+
.wui-error-summary:focus {
|
|
771
|
+
outline: none;
|
|
772
|
+
box-shadow: 0 0 0 3px var(--color-danger-soft), 0 0 0 4px var(--alert-accent);
|
|
773
|
+
}
|
|
774
|
+
.wui-error-summary-title {
|
|
775
|
+
margin: 0 0 6px;
|
|
776
|
+
font-size: 15px;
|
|
777
|
+
font-weight: 600;
|
|
778
|
+
line-height: 1.4;
|
|
779
|
+
color: var(--alert-text);
|
|
780
|
+
}
|
|
781
|
+
.wui-error-summary-list {
|
|
782
|
+
margin: 0;
|
|
783
|
+
padding-left: 20px;
|
|
784
|
+
}
|
|
785
|
+
.wui-error-summary-list li + li {
|
|
786
|
+
margin-top: 2px;
|
|
787
|
+
}
|
|
788
|
+
.wui-error-summary-list a {
|
|
789
|
+
color: var(--alert-text);
|
|
790
|
+
text-decoration: underline;
|
|
791
|
+
text-underline-offset: 2px;
|
|
792
|
+
}
|
|
793
|
+
|
|
768
794
|
// Borderless variant (`bordered={false}` / `bordered="false"`): drop the soft ring and
|
|
769
795
|
// keep only the tint fill. `transparent` rather than `border: 0` so the box keeps its
|
|
770
796
|
// 1px so nothing shifts when toggling the border on and off.
|