@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/theme",
3
- "version": "0.17.3",
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": {
@@ -38,7 +38,7 @@
38
38
  // so they must stay in the layer that loads after Bootstrap.
39
39
  .card-title {
40
40
  margin: 0;
41
- font-size: 15px;
41
+ font-size: 16px;
42
42
  font-weight: 600;
43
43
  line-height: 1.3;
44
44
  color: var(--color-text);
@@ -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,
@@ -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;
@@ -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: 17px;
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 lays the input on the LEFT with the chips filling and wrapping to its
445
- // right (its template renders the input before the chips). Multiselect keeps its
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
@@ -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.