@ultimat3/ui 29.0.0 → 29.2.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": "@ultimat3/ui",
3
- "version": "29.0.0",
3
+ "version": "29.2.0",
4
4
  "description": "SolidJS design system: semantic design tokens, dark/RTL-ready SCSS modules, a11y primitives",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -48,10 +48,10 @@
48
48
  "icons": "bun run src/icons/build-icons.ts"
49
49
  },
50
50
  "dependencies": {
51
- "@ultimat3/core": "29.0.0",
52
- "@ultimat3/i18n": "29.0.0",
53
- "@ultimat3/money": "29.0.0",
54
- "@ultimat3/time": "29.0.0"
51
+ "@ultimat3/core": "29.2.0",
52
+ "@ultimat3/i18n": "29.2.0",
53
+ "@ultimat3/money": "29.2.0",
54
+ "@ultimat3/time": "29.2.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "solid-js": "^1.9.0"
package/src/global.scss CHANGED
@@ -30,11 +30,17 @@
30
30
  // The client router's route-presented modal (`@ultimat3/render`'s `NAVIGATION_MODAL_ATTRIBUTE`, a
31
31
  // route declaring `navigation: 'modal'`): a native <dialog> holding the page's `<main>`, styled as
32
32
  // `Dialog` is. Global because no component renders it — the router does, and `render` cannot
33
- // import `ui` (one tier) — so the name is held equal by render's `navigation-modal-style.test.ts`.
34
- // `:where()`: an app restyles it without out-shouting a selector.
33
+ // import `ui` (one tier) — so the names are held equal by render's `navigation-modal-style.test.ts`.
34
+ // `:where()`: an app restyles it without out-shouting a selector. Opened as a top-layer popover
35
+ // (`show()` + `showPopover()`, never `showModal()`, whose inert document recalculates the style of
36
+ // every element beneath), so it is placed here as `showModal()` would have placed it.
35
37
  :where(dialog[data-x-modal]) {
38
+ position: fixed;
39
+ inset: 0;
36
40
  inline-size: min(100% - #{t.space(8)}, 40rem);
41
+ block-size: fit-content;
37
42
  max-block-size: min(88dvh, 60rem);
43
+ margin: auto;
38
44
  padding: t.space(5);
39
45
  border: 1px solid t.role('line');
40
46
  border-radius: t.radius(lg);
@@ -51,6 +57,87 @@
51
57
  }
52
58
  }
53
59
 
60
+ // The frame's close button (`NAVIGATION_MODAL_CLOSE_ATTRIBUTE`): after the content in the DOM, at
61
+ // the dialog's top corner on screen. Its name is its `aria-label`; the cross is drawn, not written.
62
+ :where([data-x-modal-close]) {
63
+ position: absolute;
64
+ inset-block-start: t.space(3);
65
+ inset-inline-end: t.space(3);
66
+ inline-size: t.space(8);
67
+ block-size: t.space(8);
68
+ padding: 0;
69
+ border: 0;
70
+ border-radius: t.radius(md);
71
+ background: transparent;
72
+ color: t.role('fg-muted');
73
+ cursor: pointer;
74
+
75
+ &::before,
76
+ &::after {
77
+ content: '';
78
+ position: absolute;
79
+ inset-block-start: 50%;
80
+ inset-inline-start: 25%;
81
+ inline-size: 50%;
82
+ block-size: t.rem(2px);
83
+ border-radius: t.radius(full);
84
+ background: currentcolor;
85
+ rotate: 45deg;
86
+ }
87
+
88
+ &::after {
89
+ rotate: -45deg;
90
+ }
91
+
92
+ &:hover {
93
+ background: t.role('bg-soft');
94
+ color: t.role('fg');
95
+ }
96
+ }
97
+
98
+ // The modal's own heading clears the close button.
99
+ :where([data-x-modal-body] > :is(h1, h2):first-child) {
100
+ padding-inline-end: t.space(8);
101
+ }
102
+
103
+ // The body while the modal's document is on its way (`aria-busy`): a skeleton the size of a short
104
+ // form, built from surface roles as `Skeleton` is, so the dialog does not jump far when it fills.
105
+ :where([data-x-modal-skeleton]) {
106
+ display: grid;
107
+ gap: t.space(3);
108
+ min-block-size: 12rem;
109
+
110
+ &::before,
111
+ &::after {
112
+ content: '';
113
+ display: block;
114
+ border-radius: t.radius(md);
115
+ background: linear-gradient(
116
+ 90deg,
117
+ t.role('bg-soft') 25%,
118
+ t.role('line') 37%,
119
+ t.role('bg-soft') 63%
120
+ )
121
+ 0 0 / 200% 100%;
122
+ animation: ultimate-modal-shimmer calc(#{t.duration(slower)} * 2) linear infinite;
123
+ }
124
+
125
+ &::before {
126
+ inline-size: 60%;
127
+ block-size: t.space(6);
128
+ }
129
+
130
+ &::after {
131
+ block-size: 8rem;
132
+ }
133
+ }
134
+
135
+ @keyframes ultimate-modal-shimmer {
136
+ to {
137
+ background-position: -200% 0;
138
+ }
139
+ }
140
+
54
141
  @keyframes ultimate-modal-in {
55
142
  from {
56
143
  opacity: 0;
@@ -135,6 +135,13 @@ html:has(dialog:modal) {
135
135
  overflow: hidden;
136
136
  }
137
137
 
138
+ // The router's route-presented modal is a top-layer popover, never `:modal` (`global.scss` says
139
+ // why), so it locks the scroll by its own name. A child combinator from <body>: the one place the
140
+ // router appends it, so the `:has()` is answered without walking the page.
141
+ :where(html:has(> body > dialog[data-x-modal][open])) {
142
+ overflow: hidden;
143
+ }
144
+
138
145
  // The live region `announce()` (`../a11y.ts`) appends to <body>, hidden from here rather than
139
146
  // through `element.style`: a style a script applies at runtime is not among the inline styles the
140
147
  // CSP the framework itself sends has hashed, so it logged a `style-src` violation on every page