@webority/theme 0.9.6 → 0.9.8

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.9.6",
3
+ "version": "0.9.8",
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": {
@@ -58,6 +58,28 @@
58
58
  --bs-btn-active-border-color: transparent;
59
59
  }
60
60
 
61
+ // A soft-tinted affirmative — the success counterpart of .btn-secondary. Dark success text on a
62
+ // faint success fill, so it reads as "resolved / confirmed" without the weight of a solid green.
63
+ .btn-success {
64
+ --bs-btn-color: var(--color-success-text);
65
+ --bs-btn-bg: color-mix(in srgb, var(--color-success) 14%, transparent);
66
+ --bs-btn-border-color: transparent;
67
+ --bs-btn-hover-color: var(--color-success-text);
68
+ --bs-btn-hover-bg: color-mix(in srgb, var(--color-success) 20%, transparent);
69
+ --bs-btn-hover-border-color: transparent;
70
+ --bs-btn-active-color: var(--color-success-text);
71
+ --bs-btn-active-bg: color-mix(in srgb, var(--color-success) 28%, transparent);
72
+ --bs-btn-active-border-color: transparent;
73
+ }
74
+
75
+ // A button-shaped label that does nothing: keeps the .btn chrome but takes no pointer, so it
76
+ // neither clicks nor lifts on hover. Used for a resolved confirmation (AppButton `static`) that
77
+ // must sit exactly where its action button was.
78
+ .btn-static {
79
+ cursor: default;
80
+ pointer-events: none;
81
+ }
82
+
61
83
  .btn-danger {
62
84
  // White-on-red destructive look via the --wui-danger-strong token: white fails AA on the
63
85
  // brighter danger fills (#F8285A → 3.86:1, #E82646 → 4.38:1), so the button uses the
@@ -943,6 +943,14 @@ button.datepicker-month:hover {
943
943
  // Match AppInput's soft invalid border exactly (danger at 30%, mirroring primary-30 focus).
944
944
  border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
945
945
  }
946
+ // The live-validation error renders INSIDE <wui-phone> (after .phone-input-wrap), not as a
947
+ // direct child of .field — so .field's 4px column gap can't separate it from the control, and
948
+ // .field-error's -2px pull then makes it touch the input. <wui-phone> is display:block with no
949
+ // gap of its own, so restore the same effective 2px control→error gap the plain field gives,
950
+ // matching AppInput.
951
+ .app-phone-number-input wui-phone > .field-error {
952
+ margin-top: 2px;
953
+ }
946
954
  // Popover-field controls (date / date-range / time) carry aria-invalid on the HOST element —
947
955
  // React/Razor set it from the `error` prop — while the visible border lives on the inner trigger
948
956
  // button. Redden that trigger, and keep it red while the popover is open (mirroring AppInput,
@@ -657,6 +657,26 @@ textarea.form-control.autosize {
657
657
  color: var(--color-primary);
658
658
  }
659
659
 
660
+ // Trailing action beside the field — a control (a labelled button, or a confirmation once the
661
+ // value resolves) that sits OUTSIDE the input border. The field flexes to fill the row; the
662
+ // action keeps its natural width and centres against the control.
663
+ .input-action-row {
664
+ display: flex;
665
+ align-items: center;
666
+ gap: 0.5rem;
667
+ }
668
+ .input-action-row > .input-wrap,
669
+ .input-action-row > .input-icon {
670
+ flex: 1 1 auto;
671
+ min-width: 0;
672
+ }
673
+ .input-action {
674
+ flex: 0 0 auto;
675
+ display: flex;
676
+ align-items: center;
677
+ white-space: nowrap;
678
+ }
679
+
660
680
  // Password reveal toggle (place inside .input-wrap).
661
681
  .input.pw {
662
682
  padding-right: 44px;
@@ -112,6 +112,40 @@
112
112
  .table-ledger tbody tr:hover td.wui-table-empty:has(> .tbl-empty) {
113
113
  background: transparent;
114
114
  }
115
+ // Keep the empty state on screen. An empty table has no data to scroll to, yet its
116
+ // header row can still be wider than the viewport (many columns, or wide header
117
+ // labels), so `.table-wrap` would scroll horizontally and the centred empty message —
118
+ // spanning that full width in a colSpan cell — would slide off screen on a narrow
119
+ // screen. Keyed off the empty body itself (`.tbl-empty` for a rich AppTableEmptyState,
120
+ // `.tbl-empty-plain` for a plain string — both surfaces render one), lay the empty
121
+ // table out at the visible wrap width and drop the scroll, so the message stays centred
122
+ // and fully in view. This is the "do it without turning the scroll container into a
123
+ // query container" path the pin note on `.table-wrap:has(> .table-ledger)` calls for —
124
+ // it replaces the earlier `container-type` + `100cqw` pin, which collapsed the wrap as a
125
+ // flex/grid item. Scoped to the empty body, so a table WITH rows is untouched and still
126
+ // scrolls its columns.
127
+ // Keyed on the empty CELL (`td.wui-table-empty`, the one element unique to the empty
128
+ // state on both surfaces) with CHILD combinators, NOT a loose descendant `:has(.tbl-empty)`.
129
+ // The child chain matches only a table's OWN empty body — so an expandable row whose
130
+ // nested table happens to be empty can never force the outer, data-bearing table into
131
+ // this layout. A data table (no empty cell of its own) is untouched and still scrolls.
132
+ .table-wrap:has(> table > tbody > tr > td.wui-table-empty) {
133
+ overflow-x: hidden;
134
+ }
135
+ .table:has(> tbody > tr > td.wui-table-empty) {
136
+ width: 100%;
137
+ min-width: 0;
138
+ table-layout: fixed;
139
+ }
140
+ // Under the fixed layout above the columns are narrow, so a long header label would
141
+ // spill out of its cell and back into horizontal overflow — the very thing we just
142
+ // removed. Clip each header to its column (ellipsis) in the empty state; short labels
143
+ // are unaffected, and the data table keeps its natural header sizing.
144
+ .table:has(> tbody > tr > td.wui-table-empty) > thead th {
145
+ overflow: hidden;
146
+ white-space: nowrap;
147
+ text-overflow: ellipsis;
148
+ }
115
149
 
116
150
  // ── Table (base) ──
117
151
  // The shared data-table look, on the plain `.table` class Bootstrap already
@@ -547,6 +547,19 @@
547
547
  animation: wui-toast-in 0.24s ease;
548
548
  }
549
549
 
550
+ // A single-line toast — bare message, no title / action / progress stack — centers its icon,
551
+ // text and close as one row. The flex-start default above exists for the multi-line variants
552
+ // (title+message, an action button, a progress track), where the icon must sit on the first
553
+ // line rather than float to the middle of a tall card.
554
+ .wui-toast:not(:has(.wui-toast-title)):not(:has(.wui-toast-action)):not(:has(.wui-toast-task)) {
555
+ align-items: center;
556
+ }
557
+ // The 1px top nudges align the icon/close with the first text line under flex-start; once the
558
+ // row is centered they would push those items a touch low, so zero them here.
559
+ .wui-toast:not(:has(.wui-toast-title)):not(:has(.wui-toast-action)):not(:has(.wui-toast-task)) :is(.wui-toast-icon, .wui-toast-close) {
560
+ margin-top: 0;
561
+ }
562
+
550
563
  // While a swipe is in progress the toast follows the pointer with no transition.
551
564
  .wui-toast--dragging {
552
565
  transition: none;
@@ -164,6 +164,13 @@
164
164
  align-items: center;
165
165
  justify-content: space-between;
166
166
  gap: 16px;
167
+ // Content-driven wrap: while the title block and actions both fit, space-between keeps the
168
+ // actions pinned right; once a long title needs the full width they can no longer share the
169
+ // row, so the actions drop onto their own line — left-aligned, since space-between places a
170
+ // lone wrapped item at flex-start — instead of being crushed against the title. The gap above
171
+ // doubles as the row-gap to that dropped line. (Below lg they stack unconditionally, further
172
+ // down; this is the any-width, content-driven case.)
173
+ flex-wrap: wrap;
167
174
  }
168
175
  // Left block: an optional leading icon tile + the title stack, kept together.
169
176
  .page-header-lead {