@webority/theme 0.6.10 → 0.6.12
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/wui/_choice.scss +299 -0
- package/scss/wui/_controls.scss +131 -1
- package/scss/wui/_display.scss +73 -2
- package/scss/wui/_navigation.scss +71 -2
- package/scss/wui/_typography.scss +18 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
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/_choice.scss
CHANGED
|
@@ -201,6 +201,24 @@
|
|
|
201
201
|
min-height: var(--wui-control-height-lg);
|
|
202
202
|
font-size: 16px;
|
|
203
203
|
}
|
|
204
|
+
// Multi-select grows with its chips, so a size sets the FLOOR (min-height from the rule
|
|
205
|
+
// above) but never a fixed height — reset height:auto so a second row of chips can appear.
|
|
206
|
+
.search-select-sm .search-select-multi,
|
|
207
|
+
.search-select-lg .search-select-multi {
|
|
208
|
+
height: auto;
|
|
209
|
+
}
|
|
210
|
+
// AppSelectMini is <wui-select compact> (a 32px picker). Its sizes are compact-scaled —
|
|
211
|
+
// the compound selector outranks both the plain size rule and the compact rule.
|
|
212
|
+
.search-select-compact.search-select-sm .search-select-control {
|
|
213
|
+
height: 28px;
|
|
214
|
+
min-height: 28px;
|
|
215
|
+
font-size: 12px;
|
|
216
|
+
}
|
|
217
|
+
.search-select-compact.search-select-lg .search-select-control {
|
|
218
|
+
height: 38px;
|
|
219
|
+
min-height: 38px;
|
|
220
|
+
font-size: 14px;
|
|
221
|
+
}
|
|
204
222
|
|
|
205
223
|
// Clear (✕) — sits between the value and the caret, shown only once a value is
|
|
206
224
|
// chosen. It is a <span role="button"> inside the trigger <button> (a nested
|
|
@@ -227,6 +245,35 @@
|
|
|
227
245
|
color: var(--color-text);
|
|
228
246
|
}
|
|
229
247
|
|
|
248
|
+
// Leading icon + static prefix (e.g. a funnel glyph, or "Sort:") — cluster at the
|
|
249
|
+
// start of the control, before the value. Mainly for the mini toolbar picker.
|
|
250
|
+
.search-select-icon {
|
|
251
|
+
flex-shrink: 0;
|
|
252
|
+
display: inline-flex;
|
|
253
|
+
color: var(--color-text-muted);
|
|
254
|
+
font-size: 15px;
|
|
255
|
+
}
|
|
256
|
+
.search-select-prefix {
|
|
257
|
+
flex-shrink: 0;
|
|
258
|
+
color: var(--color-text-subtle);
|
|
259
|
+
font-weight: 400;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Per-option leading glyph — wraps icon + label as one node (menu row, the selected
|
|
263
|
+
// value in the control, and chips all render through optionLabelHtml). The icon inherits
|
|
264
|
+
// the row's colour so it recolours with the label on hover / when selected.
|
|
265
|
+
.wui-opt-label {
|
|
266
|
+
display: inline-flex;
|
|
267
|
+
align-items: center;
|
|
268
|
+
gap: 8px;
|
|
269
|
+
min-width: 0;
|
|
270
|
+
vertical-align: middle;
|
|
271
|
+
}
|
|
272
|
+
.wui-opt-label > .bi {
|
|
273
|
+
flex-shrink: 0;
|
|
274
|
+
font-size: 15px;
|
|
275
|
+
}
|
|
276
|
+
|
|
230
277
|
// AppSelectMini — the compact single-purpose picker (page size, a range switch). It shares
|
|
231
278
|
// every behaviour with AppSelect and differs only in metrics.
|
|
232
279
|
//
|
|
@@ -380,14 +427,47 @@
|
|
|
380
427
|
// styled).
|
|
381
428
|
.datepicker {
|
|
382
429
|
position: relative;
|
|
430
|
+
// A date is one short value, so the field defaults to a fixed width instead of filling its
|
|
431
|
+
// column. Override per use with a .date-w-* class or the --wui-datepicker-width variable;
|
|
432
|
+
// 100% restores the old full-width, form-aligned behaviour.
|
|
433
|
+
width: var(--wui-datepicker-width, 15rem);
|
|
434
|
+
max-width: 100%;
|
|
383
435
|
}
|
|
384
436
|
.datepicker-trigger {
|
|
437
|
+
display: flex;
|
|
438
|
+
align-items: center;
|
|
439
|
+
justify-content: space-between;
|
|
440
|
+
gap: 8px;
|
|
385
441
|
text-align: left;
|
|
386
442
|
cursor: pointer;
|
|
387
443
|
}
|
|
444
|
+
// The label takes the room; the calendar glyph is pinned right and never shrinks.
|
|
445
|
+
.datepicker-trigger [data-trigger-label] {
|
|
446
|
+
overflow: hidden;
|
|
447
|
+
white-space: nowrap;
|
|
448
|
+
text-overflow: ellipsis;
|
|
449
|
+
}
|
|
450
|
+
.datepicker-trigger > .bi {
|
|
451
|
+
flex-shrink: 0;
|
|
452
|
+
color: var(--color-text-subtle);
|
|
453
|
+
}
|
|
388
454
|
.datepicker-trigger:not(.has-value) {
|
|
389
455
|
color: var(--color-text-subtle);
|
|
390
456
|
}
|
|
457
|
+
// Width presets — set the variable on the field (or the bare control) and it cascades down
|
|
458
|
+
// to .datepicker. Consumers apply these through the component's className / class.
|
|
459
|
+
.date-w-sm {
|
|
460
|
+
--wui-datepicker-width: 11rem;
|
|
461
|
+
}
|
|
462
|
+
.date-w-md {
|
|
463
|
+
--wui-datepicker-width: 15rem;
|
|
464
|
+
}
|
|
465
|
+
.date-w-lg {
|
|
466
|
+
--wui-datepicker-width: 20rem;
|
|
467
|
+
}
|
|
468
|
+
.date-w-full {
|
|
469
|
+
--wui-datepicker-width: 100%;
|
|
470
|
+
}
|
|
391
471
|
.datepicker.open .datepicker-trigger {
|
|
392
472
|
background-color: var(--color-bg-muted);
|
|
393
473
|
border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
|
|
@@ -439,10 +519,25 @@
|
|
|
439
519
|
margin-bottom: 14px;
|
|
440
520
|
}
|
|
441
521
|
.datepicker-month {
|
|
522
|
+
display: inline-flex;
|
|
523
|
+
align-items: center;
|
|
524
|
+
gap: 6px;
|
|
525
|
+
border: none;
|
|
526
|
+
background: transparent;
|
|
527
|
+
padding: 6px 12px;
|
|
528
|
+
border-radius: 999px;
|
|
442
529
|
font-size: 15px;
|
|
443
530
|
font-weight: 600;
|
|
444
531
|
color: var(--color-text);
|
|
445
532
|
letter-spacing: -0.01em;
|
|
533
|
+
cursor: pointer;
|
|
534
|
+
transition:
|
|
535
|
+
background 0.14s,
|
|
536
|
+
color 0.14s;
|
|
537
|
+
}
|
|
538
|
+
button.datepicker-month:hover {
|
|
539
|
+
background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
|
|
540
|
+
color: var(--color-primary);
|
|
446
541
|
}
|
|
447
542
|
.datepicker-nav {
|
|
448
543
|
display: inline-flex;
|
|
@@ -566,6 +661,210 @@
|
|
|
566
661
|
cursor: not-allowed;
|
|
567
662
|
}
|
|
568
663
|
|
|
664
|
+
// Month / year drill-down grids — the header opens these, then drills back to days.
|
|
665
|
+
.datepicker-cells {
|
|
666
|
+
display: grid;
|
|
667
|
+
grid-template-columns: repeat(3, 1fr);
|
|
668
|
+
gap: 6px;
|
|
669
|
+
animation: datepicker-view-in 0.18s ease;
|
|
670
|
+
}
|
|
671
|
+
.datepicker-cell {
|
|
672
|
+
display: inline-flex;
|
|
673
|
+
align-items: center;
|
|
674
|
+
justify-content: center;
|
|
675
|
+
height: 48px;
|
|
676
|
+
border: none;
|
|
677
|
+
border-radius: var(--radius-lg, 12px);
|
|
678
|
+
background: transparent;
|
|
679
|
+
font-size: 13px;
|
|
680
|
+
font-weight: 500;
|
|
681
|
+
color: var(--color-text);
|
|
682
|
+
cursor: pointer;
|
|
683
|
+
transition:
|
|
684
|
+
background 0.12s,
|
|
685
|
+
color 0.12s,
|
|
686
|
+
box-shadow 0.12s;
|
|
687
|
+
}
|
|
688
|
+
.datepicker-cell:hover:not(:disabled):not(.is-selected) {
|
|
689
|
+
background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
|
|
690
|
+
color: var(--color-primary);
|
|
691
|
+
}
|
|
692
|
+
.datepicker-cell.is-today {
|
|
693
|
+
box-shadow: inset 0 0 0 1.5px var(--color-primary);
|
|
694
|
+
color: var(--color-primary);
|
|
695
|
+
font-weight: 700;
|
|
696
|
+
}
|
|
697
|
+
.datepicker-cell.is-selected {
|
|
698
|
+
background: var(--color-primary);
|
|
699
|
+
color: #fff;
|
|
700
|
+
font-weight: 600;
|
|
701
|
+
}
|
|
702
|
+
.datepicker-cell:disabled {
|
|
703
|
+
color: var(--color-border-strong);
|
|
704
|
+
cursor: not-allowed;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Keyboard focus ring — days and drill-down cells share it, drawn as a halo so it reads on
|
|
708
|
+
// both the transparent and the filled (selected) states.
|
|
709
|
+
.datepicker-day:focus-visible,
|
|
710
|
+
.datepicker-cell:focus-visible {
|
|
711
|
+
outline: none;
|
|
712
|
+
box-shadow:
|
|
713
|
+
0 0 0 2px var(--color-surface),
|
|
714
|
+
0 0 0 4px var(--color-primary);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// The panel view fades+scales in on every drill; each month nav slides in from its direction.
|
|
718
|
+
@keyframes datepicker-view-in {
|
|
719
|
+
from {
|
|
720
|
+
opacity: 0;
|
|
721
|
+
transform: scale(0.96);
|
|
722
|
+
}
|
|
723
|
+
to {
|
|
724
|
+
opacity: 1;
|
|
725
|
+
transform: scale(1);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
.datepicker-grid.slide-next {
|
|
729
|
+
animation: datepicker-slide-next 0.2s ease;
|
|
730
|
+
}
|
|
731
|
+
.datepicker-grid.slide-prev {
|
|
732
|
+
animation: datepicker-slide-prev 0.2s ease;
|
|
733
|
+
}
|
|
734
|
+
@keyframes datepicker-slide-next {
|
|
735
|
+
from {
|
|
736
|
+
opacity: 0.35;
|
|
737
|
+
transform: translateX(10px);
|
|
738
|
+
}
|
|
739
|
+
to {
|
|
740
|
+
opacity: 1;
|
|
741
|
+
transform: translateX(0);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
@keyframes datepicker-slide-prev {
|
|
745
|
+
from {
|
|
746
|
+
opacity: 0.35;
|
|
747
|
+
transform: translateX(-10px);
|
|
748
|
+
}
|
|
749
|
+
to {
|
|
750
|
+
opacity: 1;
|
|
751
|
+
transform: translateX(0);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
@media (prefers-reduced-motion: reduce) {
|
|
755
|
+
.datepicker-pop,
|
|
756
|
+
.datepicker-cells,
|
|
757
|
+
.datepicker-grid.slide-next,
|
|
758
|
+
.datepicker-grid.slide-prev {
|
|
759
|
+
animation: none;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Date RANGE — start/end field with a two-month popover. Reuses the .datepicker-day cell
|
|
764
|
+
// language; adds the start / in-range / end painting on top.
|
|
765
|
+
.daterange {
|
|
766
|
+
position: relative;
|
|
767
|
+
// Wider default than a single date, but honours the same --wui-datepicker-width var and
|
|
768
|
+
// .date-w-* presets so both range and single fields size the same way.
|
|
769
|
+
width: var(--wui-datepicker-width, 20rem);
|
|
770
|
+
max-width: 100%;
|
|
771
|
+
}
|
|
772
|
+
.daterange-trigger {
|
|
773
|
+
display: flex;
|
|
774
|
+
align-items: center;
|
|
775
|
+
justify-content: space-between;
|
|
776
|
+
gap: 8px;
|
|
777
|
+
text-align: left;
|
|
778
|
+
cursor: pointer;
|
|
779
|
+
}
|
|
780
|
+
.daterange-trigger [data-range-label] {
|
|
781
|
+
overflow: hidden;
|
|
782
|
+
white-space: nowrap;
|
|
783
|
+
text-overflow: ellipsis;
|
|
784
|
+
}
|
|
785
|
+
.daterange-trigger > .bi {
|
|
786
|
+
flex-shrink: 0;
|
|
787
|
+
color: var(--color-text-subtle);
|
|
788
|
+
}
|
|
789
|
+
.daterange-trigger:not(.has-value) {
|
|
790
|
+
color: var(--color-text-subtle);
|
|
791
|
+
}
|
|
792
|
+
.daterange.open .daterange-trigger {
|
|
793
|
+
background-color: var(--color-bg-muted);
|
|
794
|
+
border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
|
|
795
|
+
box-shadow: none;
|
|
796
|
+
}
|
|
797
|
+
.daterange-pop {
|
|
798
|
+
position: fixed;
|
|
799
|
+
z-index: 1600;
|
|
800
|
+
width: 620px;
|
|
801
|
+
max-width: calc(100vw - 16px);
|
|
802
|
+
padding: 18px;
|
|
803
|
+
background: var(--color-surface);
|
|
804
|
+
border: 1px solid var(--color-border);
|
|
805
|
+
border-radius: var(--radius-xl);
|
|
806
|
+
corner-shape: squircle;
|
|
807
|
+
box-shadow: var(--shadow-overlay);
|
|
808
|
+
animation: datepicker-in 0.14s ease;
|
|
809
|
+
}
|
|
810
|
+
.daterange-pop.drop-up {
|
|
811
|
+
animation: datepicker-in-up 0.14s ease;
|
|
812
|
+
}
|
|
813
|
+
// The two nav buttons bracket the calendars — one at each edge of the header row.
|
|
814
|
+
.daterange-head {
|
|
815
|
+
display: flex;
|
|
816
|
+
align-items: center;
|
|
817
|
+
justify-content: space-between;
|
|
818
|
+
margin-bottom: 6px;
|
|
819
|
+
}
|
|
820
|
+
.daterange-cals {
|
|
821
|
+
display: flex;
|
|
822
|
+
gap: 24px;
|
|
823
|
+
}
|
|
824
|
+
.daterange-cal {
|
|
825
|
+
flex: 1;
|
|
826
|
+
min-width: 0;
|
|
827
|
+
}
|
|
828
|
+
.datepicker-month-label {
|
|
829
|
+
text-align: center;
|
|
830
|
+
font-size: 15px;
|
|
831
|
+
font-weight: 600;
|
|
832
|
+
color: var(--color-text);
|
|
833
|
+
letter-spacing: -0.01em;
|
|
834
|
+
margin-bottom: 12px;
|
|
835
|
+
}
|
|
836
|
+
// Days inside the chosen span get a soft rail; the two endpoints get the solid fill.
|
|
837
|
+
.datepicker-day.in-range {
|
|
838
|
+
background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 12%, transparent));
|
|
839
|
+
color: var(--color-primary);
|
|
840
|
+
border-radius: 6px;
|
|
841
|
+
}
|
|
842
|
+
.datepicker-day.is-range-start,
|
|
843
|
+
.datepicker-day.is-range-end {
|
|
844
|
+
background: var(--color-primary);
|
|
845
|
+
color: #fff;
|
|
846
|
+
font-weight: 600;
|
|
847
|
+
}
|
|
848
|
+
.daterange-hint {
|
|
849
|
+
font-size: 12px;
|
|
850
|
+
color: var(--color-text-subtle);
|
|
851
|
+
}
|
|
852
|
+
@media (max-width: 660px) {
|
|
853
|
+
// Below the two-month width, stack the calendars so the popover still fits the viewport.
|
|
854
|
+
.daterange-pop {
|
|
855
|
+
width: 320px;
|
|
856
|
+
}
|
|
857
|
+
.daterange-cals {
|
|
858
|
+
flex-direction: column;
|
|
859
|
+
gap: 16px;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
@media (prefers-reduced-motion: reduce) {
|
|
863
|
+
.daterange-pop {
|
|
864
|
+
animation: none;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
569
868
|
// ── Phone input ──
|
|
570
869
|
.app-phone-number-input {
|
|
571
870
|
width: 100%;
|
package/scss/wui/_controls.scss
CHANGED
|
@@ -172,6 +172,134 @@
|
|
|
172
172
|
vertical-align: middle;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
// ── Premium checkbox / radio / switch ──
|
|
176
|
+
// AppCheckbox / AppRadio / AppSwitch render Bootstrap's .form-check markup, whose default
|
|
177
|
+
// input is a plain 16px box. This restyles it to the Webority selection-control language
|
|
178
|
+
// (see .lx-check above): a 20px squircle box, brand fill, a crisp white tick, a soft primary
|
|
179
|
+
// hover ring + focus ring, and a themed switch. One block upgrades all three, both surfaces.
|
|
180
|
+
.form-check {
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
gap: 8px;
|
|
184
|
+
min-height: 0;
|
|
185
|
+
margin-bottom: 0;
|
|
186
|
+
padding-left: 0;
|
|
187
|
+
}
|
|
188
|
+
.form-check-inline {
|
|
189
|
+
display: inline-flex;
|
|
190
|
+
margin-right: 16px;
|
|
191
|
+
}
|
|
192
|
+
.form-check-input {
|
|
193
|
+
box-sizing: border-box;
|
|
194
|
+
flex: none;
|
|
195
|
+
float: none;
|
|
196
|
+
width: 20px;
|
|
197
|
+
height: 20px;
|
|
198
|
+
margin: 0;
|
|
199
|
+
appearance: none;
|
|
200
|
+
-webkit-appearance: none;
|
|
201
|
+
border: 1.6px solid var(--color-border-strong);
|
|
202
|
+
background-color: var(--color-surface);
|
|
203
|
+
background-repeat: no-repeat;
|
|
204
|
+
background-position: center;
|
|
205
|
+
background-size: 15px 15px;
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
transition:
|
|
208
|
+
border-color 0.15s ease,
|
|
209
|
+
background-color 0.15s ease,
|
|
210
|
+
box-shadow 0.15s ease,
|
|
211
|
+
background-position 0.15s ease;
|
|
212
|
+
}
|
|
213
|
+
// Bootstrap floats the input into the label's left padding via `.form-check .form-check-input`
|
|
214
|
+
// (specificity 0,2,0 — it outranks the rule above), which drags the box a negative margin
|
|
215
|
+
// left, out of the card. Our flex row places it directly; undo the float and negative margin
|
|
216
|
+
// at matching specificity.
|
|
217
|
+
.form-check .form-check-input {
|
|
218
|
+
float: none;
|
|
219
|
+
margin: 0;
|
|
220
|
+
}
|
|
221
|
+
.form-check-input[type="checkbox"] {
|
|
222
|
+
border-radius: var(--radius-sm);
|
|
223
|
+
corner-shape: squircle;
|
|
224
|
+
}
|
|
225
|
+
.form-check-input[type="radio"] {
|
|
226
|
+
border-radius: 50%;
|
|
227
|
+
}
|
|
228
|
+
.form-check-input:hover:not(:disabled):not(:checked) {
|
|
229
|
+
border-color: var(--color-primary);
|
|
230
|
+
box-shadow: 0 0 0 4px var(--color-primary-soft);
|
|
231
|
+
}
|
|
232
|
+
.form-check-input:focus-visible {
|
|
233
|
+
outline: none;
|
|
234
|
+
border-color: var(--color-primary);
|
|
235
|
+
box-shadow: 0 0 0 3px var(--color-primary-soft);
|
|
236
|
+
}
|
|
237
|
+
.form-check-input:checked {
|
|
238
|
+
border-color: var(--color-primary);
|
|
239
|
+
background-color: var(--color-primary);
|
|
240
|
+
}
|
|
241
|
+
.form-check-input[type="checkbox"]:checked {
|
|
242
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7'/%3E%3C/svg%3E");
|
|
243
|
+
}
|
|
244
|
+
.form-check-input[type="checkbox"]:indeterminate {
|
|
245
|
+
border-color: var(--color-primary);
|
|
246
|
+
background-color: var(--color-primary);
|
|
247
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
|
|
248
|
+
}
|
|
249
|
+
.form-check-input[type="radio"]:checked {
|
|
250
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='5.5' fill='white'/%3E%3C/svg%3E");
|
|
251
|
+
background-size: 20px 20px;
|
|
252
|
+
}
|
|
253
|
+
.form-check-input:disabled {
|
|
254
|
+
cursor: not-allowed;
|
|
255
|
+
opacity: 0.55;
|
|
256
|
+
background-color: var(--color-bg-muted);
|
|
257
|
+
border-color: var(--color-border);
|
|
258
|
+
box-shadow: none;
|
|
259
|
+
}
|
|
260
|
+
.form-check-label {
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
font-size: 14px;
|
|
263
|
+
line-height: 1.4;
|
|
264
|
+
color: var(--color-text);
|
|
265
|
+
user-select: none;
|
|
266
|
+
}
|
|
267
|
+
.form-check-input:disabled ~ .form-check-label {
|
|
268
|
+
cursor: not-allowed;
|
|
269
|
+
color: var(--color-text-subtle);
|
|
270
|
+
}
|
|
271
|
+
// Switch — a pill track with a sliding white knob.
|
|
272
|
+
.form-switch {
|
|
273
|
+
padding-left: 0;
|
|
274
|
+
}
|
|
275
|
+
.form-switch .form-check-input {
|
|
276
|
+
width: 40px;
|
|
277
|
+
height: 22px;
|
|
278
|
+
border-radius: 999px;
|
|
279
|
+
border-color: transparent;
|
|
280
|
+
background-color: var(--color-border-strong);
|
|
281
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='7' fill='white'/%3E%3C/svg%3E");
|
|
282
|
+
background-position: left center;
|
|
283
|
+
background-size: 18px 18px;
|
|
284
|
+
}
|
|
285
|
+
.form-switch .form-check-input:hover:not(:disabled):not(:checked) {
|
|
286
|
+
border-color: transparent;
|
|
287
|
+
box-shadow: 0 0 0 4px var(--color-primary-soft);
|
|
288
|
+
}
|
|
289
|
+
.form-switch .form-check-input:checked {
|
|
290
|
+
border-color: transparent;
|
|
291
|
+
background-color: var(--color-primary);
|
|
292
|
+
background-position: right center;
|
|
293
|
+
// Re-assert the knob — the checkbox :checked tick has equal specificity and would
|
|
294
|
+
// otherwise win, turning the switch into a ticked box.
|
|
295
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='7' fill='white'/%3E%3C/svg%3E");
|
|
296
|
+
}
|
|
297
|
+
@media (prefers-reduced-motion: reduce) {
|
|
298
|
+
.form-check-input {
|
|
299
|
+
transition: none;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
175
303
|
// ── Field / Input ──
|
|
176
304
|
// Inputs — always-white fill, 16px squircle, soft brand border tint on focus
|
|
177
305
|
// (no glow).
|
|
@@ -738,7 +866,8 @@ wui-select,
|
|
|
738
866
|
wui-autocomplete,
|
|
739
867
|
wui-multiselect,
|
|
740
868
|
wui-phone,
|
|
741
|
-
wui-datepicker
|
|
869
|
+
wui-datepicker,
|
|
870
|
+
wui-date-range {
|
|
742
871
|
display: block;
|
|
743
872
|
width: 100%;
|
|
744
873
|
}
|
|
@@ -754,6 +883,7 @@ wui-select[compact] {
|
|
|
754
883
|
.field > wui-multiselect,
|
|
755
884
|
.field > wui-phone,
|
|
756
885
|
.field > wui-datepicker,
|
|
886
|
+
.field > wui-date-range,
|
|
757
887
|
.field > .search-select,
|
|
758
888
|
.field > .input-with-unit,
|
|
759
889
|
.field > .input,
|
package/scss/wui/_display.scss
CHANGED
|
@@ -163,6 +163,7 @@ a.info-row:hover .info-value {
|
|
|
163
163
|
// The theme toggle (light/dark/system) is the canonical use.
|
|
164
164
|
// ============================================================================
|
|
165
165
|
.segmented {
|
|
166
|
+
position: relative;
|
|
166
167
|
display: inline-flex;
|
|
167
168
|
gap: 2px;
|
|
168
169
|
padding: 3px;
|
|
@@ -171,6 +172,8 @@ a.info-row:hover .info-value {
|
|
|
171
172
|
background: var(--color-bg-muted);
|
|
172
173
|
}
|
|
173
174
|
.segmented-option {
|
|
175
|
+
position: relative;
|
|
176
|
+
z-index: 1;
|
|
174
177
|
display: inline-flex;
|
|
175
178
|
align-items: center;
|
|
176
179
|
gap: 5px;
|
|
@@ -187,7 +190,12 @@ a.info-row:hover .info-value {
|
|
|
187
190
|
cursor: pointer;
|
|
188
191
|
transition:
|
|
189
192
|
background 0.14s ease,
|
|
190
|
-
color 0.14s ease
|
|
193
|
+
color 0.14s ease,
|
|
194
|
+
transform 0.12s ease;
|
|
195
|
+
}
|
|
196
|
+
// A quick press-in on click — a small, tactile "give".
|
|
197
|
+
.segmented-option:active {
|
|
198
|
+
transform: scale(0.95);
|
|
191
199
|
}
|
|
192
200
|
// The real radio stays in the accessibility tree and keeps focus — only its box is
|
|
193
201
|
// hidden. `display: none` would drop it from the tab order and break arrow-key
|
|
@@ -216,12 +224,75 @@ a.info-row:hover .info-value {
|
|
|
216
224
|
outline: 2px solid var(--color-primary);
|
|
217
225
|
outline-offset: 2px;
|
|
218
226
|
}
|
|
227
|
+
// Sliding thumb — one pill that glides to the selected segment. Positioned by JS (React in a
|
|
228
|
+
// layout effect, Razor in webority-ui.js), which adds .is-animated once it has measured the
|
|
229
|
+
// first segment. Without JS the per-option fill above stands in, so nothing looks broken.
|
|
230
|
+
.segmented-thumb {
|
|
231
|
+
position: absolute;
|
|
232
|
+
top: 3px;
|
|
233
|
+
bottom: 3px;
|
|
234
|
+
left: 0;
|
|
235
|
+
width: 0;
|
|
236
|
+
z-index: 0;
|
|
237
|
+
border-radius: 999px;
|
|
238
|
+
background: var(--color-primary);
|
|
239
|
+
box-shadow: 0 2px 8px color-mix(in srgb, var(--color-primary) 32%, transparent);
|
|
240
|
+
opacity: 0;
|
|
241
|
+
pointer-events: none;
|
|
242
|
+
}
|
|
243
|
+
.segmented.is-animated .segmented-thumb {
|
|
244
|
+
opacity: 1;
|
|
245
|
+
// A spring easing that overshoots a touch and settles — livelier than a flat slide.
|
|
246
|
+
transition:
|
|
247
|
+
transform 0.36s cubic-bezier(0.34, 1.42, 0.5, 1),
|
|
248
|
+
width 0.36s cubic-bezier(0.34, 1.42, 0.5, 1);
|
|
249
|
+
}
|
|
250
|
+
// While the thumb draws the fill, the checked option drops its own background (keeps white text).
|
|
251
|
+
.segmented.is-animated .segmented-option:has(.segmented-input:checked) {
|
|
252
|
+
background: transparent;
|
|
253
|
+
}
|
|
254
|
+
// Icon pop — the selected segment's glyph springs up a touch as it becomes active.
|
|
255
|
+
.segmented-option:has(.segmented-input:checked) .bi {
|
|
256
|
+
transform: scale(1.14);
|
|
257
|
+
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
258
|
+
}
|
|
259
|
+
.segmented-sm .segmented-thumb,
|
|
260
|
+
.segmented-lg .segmented-thumb {
|
|
261
|
+
top: 3px;
|
|
262
|
+
bottom: 3px;
|
|
263
|
+
}
|
|
219
264
|
.segmented-sm .segmented-option {
|
|
220
265
|
padding: 4px 9px;
|
|
221
266
|
font-size: 11px;
|
|
222
267
|
}
|
|
268
|
+
.segmented-lg .segmented-option {
|
|
269
|
+
gap: 6px;
|
|
270
|
+
padding: 8px 16px;
|
|
271
|
+
font-size: 13px;
|
|
272
|
+
}
|
|
273
|
+
// Full width — segments share the row equally (a toggle spanning a card or toolbar).
|
|
274
|
+
.segmented-full {
|
|
275
|
+
display: flex;
|
|
276
|
+
width: 100%;
|
|
277
|
+
}
|
|
278
|
+
.segmented-full .segmented-option {
|
|
279
|
+
flex: 1;
|
|
280
|
+
justify-content: center;
|
|
281
|
+
}
|
|
282
|
+
// Disabled — the whole control, or a single option that stays visible but unpickable.
|
|
283
|
+
.segmented.is-disabled {
|
|
284
|
+
opacity: 0.55;
|
|
285
|
+
pointer-events: none;
|
|
286
|
+
}
|
|
287
|
+
.segmented-option:has(.segmented-input:disabled) {
|
|
288
|
+
opacity: 0.45;
|
|
289
|
+
cursor: not-allowed;
|
|
290
|
+
}
|
|
223
291
|
@media (prefers-reduced-motion: reduce) {
|
|
224
|
-
.
|
|
292
|
+
// The thumb still jumps to the selected segment — it just does not slide, pop or press.
|
|
293
|
+
.segmented-option,
|
|
294
|
+
.segmented.is-animated .segmented-thumb,
|
|
295
|
+
.segmented-option:has(.segmented-input:checked) .bi {
|
|
225
296
|
transition: none;
|
|
226
297
|
}
|
|
227
298
|
}
|
|
@@ -268,17 +268,86 @@
|
|
|
268
268
|
background: transparent;
|
|
269
269
|
color: var(--color-text-subtle);
|
|
270
270
|
cursor: pointer;
|
|
271
|
+
// `visibility`, not opacity alone: an opacity-0 button keeps its place in the tab order,
|
|
272
|
+
// so an empty field handed a keyboard user an invisible "Clear search" that cleared
|
|
273
|
+
// nothing. Delayed on the way out so the fade still plays, immediate on the way in.
|
|
271
274
|
opacity: 0;
|
|
275
|
+
visibility: hidden;
|
|
272
276
|
pointer-events: none;
|
|
273
277
|
transition:
|
|
274
278
|
opacity 0.12s ease,
|
|
279
|
+
visibility 0s linear 0.12s,
|
|
275
280
|
color 0.12s ease;
|
|
276
281
|
}
|
|
277
|
-
.
|
|
278
|
-
|
|
282
|
+
// Revealed by CONTENT only. Revealing on :focus-within as well put a ✕ on an empty Razor
|
|
283
|
+
// field while React — which renders the button only when there is a value — showed none:
|
|
284
|
+
// the same component disagreeing across surfaces, which is the one thing this library exists
|
|
285
|
+
// to prevent.
|
|
286
|
+
.search-wrap.has-value .search-clear {
|
|
279
287
|
opacity: 1;
|
|
288
|
+
visibility: visible;
|
|
280
289
|
pointer-events: auto;
|
|
290
|
+
transition:
|
|
291
|
+
opacity 0.12s ease,
|
|
292
|
+
visibility 0s,
|
|
293
|
+
color 0.12s ease;
|
|
281
294
|
}
|
|
282
295
|
.search-clear:hover {
|
|
283
296
|
color: var(--color-text);
|
|
284
297
|
}
|
|
298
|
+
// type=search draws the browser's own cancel ✕; the component ships its own .search-clear,
|
|
299
|
+
// so the native one would double up. Remove it (WebKit/Blink).
|
|
300
|
+
.search-input::-webkit-search-cancel-button,
|
|
301
|
+
.search-input::-webkit-search-decoration {
|
|
302
|
+
-webkit-appearance: none;
|
|
303
|
+
appearance: none;
|
|
304
|
+
display: none;
|
|
305
|
+
}
|
|
306
|
+
// Sizes — sm / lg around the default, mirroring AppInput. The leading icon and clear button
|
|
307
|
+
// track the height so they stay centred and clear of the text.
|
|
308
|
+
.search-wrap.search-sm .search-input {
|
|
309
|
+
height: var(--wui-control-height-sm);
|
|
310
|
+
padding-left: 34px;
|
|
311
|
+
padding-right: 32px;
|
|
312
|
+
font-size: 13px;
|
|
313
|
+
}
|
|
314
|
+
.search-wrap.search-sm > .search-icon,
|
|
315
|
+
.search-wrap.search-sm .search-spinner {
|
|
316
|
+
left: 11px;
|
|
317
|
+
}
|
|
318
|
+
.search-wrap.search-sm .search-clear {
|
|
319
|
+
width: 22px;
|
|
320
|
+
height: 22px;
|
|
321
|
+
right: 6px;
|
|
322
|
+
}
|
|
323
|
+
.search-wrap.search-lg .search-input {
|
|
324
|
+
height: var(--wui-control-height-lg);
|
|
325
|
+
padding-left: 46px;
|
|
326
|
+
padding-right: 40px;
|
|
327
|
+
font-size: 16px;
|
|
328
|
+
}
|
|
329
|
+
.search-wrap.search-lg > .search-icon,
|
|
330
|
+
.search-wrap.search-lg .search-spinner {
|
|
331
|
+
left: 16px;
|
|
332
|
+
}
|
|
333
|
+
// Loading — a spinner replaces the leading glyph while a search is in flight.
|
|
334
|
+
.search-wrap.is-loading > .search-icon {
|
|
335
|
+
display: none;
|
|
336
|
+
}
|
|
337
|
+
.search-spinner {
|
|
338
|
+
position: absolute;
|
|
339
|
+
left: 13px;
|
|
340
|
+
top: 50%;
|
|
341
|
+
margin-top: -9px;
|
|
342
|
+
width: 18px;
|
|
343
|
+
height: 18px;
|
|
344
|
+
border: 2px solid var(--color-border-strong);
|
|
345
|
+
border-top-color: var(--color-primary);
|
|
346
|
+
border-radius: 50%;
|
|
347
|
+
animation: lx-spin 0.7s linear infinite;
|
|
348
|
+
pointer-events: none;
|
|
349
|
+
}
|
|
350
|
+
.search-input:disabled {
|
|
351
|
+
cursor: not-allowed;
|
|
352
|
+
opacity: 0.6;
|
|
353
|
+
}
|