@fluixi-ui/skin-paper 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +43 -0
- package/fonts.css +6 -0
- package/fonts.css.js +8 -0
- package/package.json +40 -0
- package/paper.css +397 -0
- package/paper.css.js +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fluixi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @fluixi-ui/skin-paper
|
|
2
|
+
|
|
3
|
+
A warm, paper-like skin for Fluixi UI: a tinted canvas, white sheets that float on it, and
|
|
4
|
+
outlines drawn as translucent half-pixel rings instead of grey borders.
|
|
5
|
+
|
|
6
|
+
Colour stays theme-driven. Surfaces come from the gray scale and rings from the foreground,
|
|
7
|
+
so `data-theme` (light, dark) and `data-accent` keep working.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import '@fluixi-ui/styles/all.css';
|
|
13
|
+
import '@fluixi-ui/skin-paper/fonts.css'; // optional: self-hosted Figtree and Bricolage Grotesque
|
|
14
|
+
import '@fluixi-ui/skin-paper/styles.css';
|
|
15
|
+
|
|
16
|
+
document.documentElement.dataset.ui = 'paper';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The attribute can sit on any subtree to scope the skin there.
|
|
20
|
+
|
|
21
|
+
## Construction
|
|
22
|
+
|
|
23
|
+
Every surface belongs to one family:
|
|
24
|
+
|
|
25
|
+
| Family | Recipe | Used by |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| field | recessed: inset ring and inset shadow | inputs, search |
|
|
28
|
+
| control | flat: ring and a 1px contact shadow | buttons, icon buttons |
|
|
29
|
+
| card | resting: ring and a soft lift | `Card`, `Surface elevation={1}` |
|
|
30
|
+
| floating | ring, contact and ambient shadow | panels, menus, dialogs (`Surface` 2 to 4) |
|
|
31
|
+
|
|
32
|
+
Inner radii are the outer radius minus the padding. In dark themes surfaces lift with a lighter
|
|
33
|
+
ring and a top highlight, since shadows go unseen.
|
|
34
|
+
|
|
35
|
+
The families are exposed as tokens (`--flx-ui-paper-card-shadow` and the like) for surfaces of
|
|
36
|
+
your own, and fluixi-ui's `--flx-ui-shadow-sm` to `-xl` map onto them.
|
|
37
|
+
|
|
38
|
+
## Type
|
|
39
|
+
|
|
40
|
+
Figtree for the interface, Bricolage Grotesque from 20px up (`h1` and `[data-display]`). Without
|
|
41
|
+
`fonts.css` both fall back to the system sans.
|
|
42
|
+
|
|
43
|
+
Rules live in the `flx.skin` layer, above `flx.components`; your own unlayered CSS still wins.
|
package/fonts.css
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The paper skin's typefaces, self-hosted: Figtree for the interface, Bricolage Grotesque
|
|
3
|
+
* for display sizes. Optional; without it the stacks fall back to the system sans.
|
|
4
|
+
*/
|
|
5
|
+
@import '@fontsource-variable/figtree';
|
|
6
|
+
@import '@fontsource-variable/bricolage-grotesque';
|
package/fonts.css.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const href = new URL('./fonts.css', import.meta.url).href;
|
|
2
|
+
if (typeof document !== 'undefined' && !document.querySelector(`link[data-flx-ui="${href}"]`)) {
|
|
3
|
+
const link = document.createElement('link');
|
|
4
|
+
link.rel = 'stylesheet';
|
|
5
|
+
link.href = href;
|
|
6
|
+
link.dataset.flxUi = href;
|
|
7
|
+
document.head.appendChild(link);
|
|
8
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluixi-ui/skin-paper",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Paper skin for Fluixi UI: warm canvas, floating white sheets, hairline rings; activate with data-ui=\"paper\"",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"registry": "https://registry.npmjs.org/",
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/fluixi/ui.git",
|
|
15
|
+
"directory": "packages/skin-paper"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": [
|
|
18
|
+
"*.css"
|
|
19
|
+
],
|
|
20
|
+
"files": [
|
|
21
|
+
"paper.css",
|
|
22
|
+
"paper.css.js",
|
|
23
|
+
"fonts.css",
|
|
24
|
+
"fonts.css.js",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
"./package.json": "./package.json",
|
|
30
|
+
"./styles.css": "./paper.css",
|
|
31
|
+
"./fonts.css": "./fonts.css"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@fontsource-variable/figtree": "^5.3.0",
|
|
35
|
+
"@fontsource-variable/bricolage-grotesque": "^5.3.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "node ../../scripts/css-loaders.mjs paper.css fonts.css"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/paper.css
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fluixi-ui/skin-paper: a warm, paper-like skin for Fluixi UI.
|
|
3
|
+
*
|
|
4
|
+
* Activate with `data-ui="paper"` on a root or any subtree. Rules live in the `flx.skin`
|
|
5
|
+
* layer, above `flx.components`; consumer CSS stays unlayered and still wins.
|
|
6
|
+
*
|
|
7
|
+
* Construction, not decoration: outlines are translucent half-pixel rings drawn with
|
|
8
|
+
* box-shadow, never grey borders, so they take the tint of whatever they sit on. Every
|
|
9
|
+
* surface belongs to one family with one recipe:
|
|
10
|
+
*
|
|
11
|
+
* field recessed: inset ring and inset shadow
|
|
12
|
+
* control flat: ring and a 1px contact shadow
|
|
13
|
+
* card resting: ring and a soft lift with negative spread
|
|
14
|
+
* floating panel, menu, dialog: ring, contact and ambient shadow, wider as it rises
|
|
15
|
+
*
|
|
16
|
+
* Inner radii are the outer radius minus the padding. Colour stays theme-driven: surfaces
|
|
17
|
+
* come from the gray scale and rings from the foreground, so `data-theme` and
|
|
18
|
+
* `data-accent` keep working.
|
|
19
|
+
*/
|
|
20
|
+
@layer flx.reset, flx.base, flx.tokens, flx.components, flx.skin, flx.fx;
|
|
21
|
+
|
|
22
|
+
@layer flx.skin {
|
|
23
|
+
[data-ui='paper'] {
|
|
24
|
+
/* Type: Figtree for the interface, Bricolage Grotesque from 20px up. fonts.css
|
|
25
|
+
self-hosts both; without it the stacks fall back to the system sans. */
|
|
26
|
+
--flx-ui-font-family-sans: 'Figtree Variable', 'Figtree', ui-sans-serif, system-ui, sans-serif;
|
|
27
|
+
--flx-ui-paper-font-display: 'Bricolage Grotesque Variable', 'Bricolage Grotesque',
|
|
28
|
+
var(--flx-ui-font-family-sans);
|
|
29
|
+
--flx-ui-font-size-xs: 12px;
|
|
30
|
+
--flx-ui-font-size-sm: 13px;
|
|
31
|
+
--flx-ui-font-size-md: 14px;
|
|
32
|
+
--flx-ui-font-size-lg: 15px;
|
|
33
|
+
--flx-ui-line-height-normal: 1.5;
|
|
34
|
+
|
|
35
|
+
--flx-ui-radius-sm: 6px;
|
|
36
|
+
--flx-ui-radius-md: 10px;
|
|
37
|
+
--flx-ui-radius-lg: 16px;
|
|
38
|
+
--flx-ui-radius-xl: 20px;
|
|
39
|
+
|
|
40
|
+
/* Controls and fields that share a bar are 36px high. */
|
|
41
|
+
--flx-ui-size-xs: 24px;
|
|
42
|
+
--flx-ui-size-sm: 30px;
|
|
43
|
+
--flx-ui-size-md: 36px;
|
|
44
|
+
--flx-ui-size-lg: 42px;
|
|
45
|
+
--flx-ui-size-xl: 48px;
|
|
46
|
+
|
|
47
|
+
/* Surfaces from the theme's gray scale. */
|
|
48
|
+
--flx-ui-paper-canvas: var(--flx-ui-gray-3);
|
|
49
|
+
--flx-ui-paper-sheet: var(--flx-ui-color-bg);
|
|
50
|
+
--flx-ui-paper-sunken: var(--flx-ui-gray-2);
|
|
51
|
+
--flx-ui-paper-field: var(--flx-ui-gray-1);
|
|
52
|
+
--flx-ui-paper-control: var(--flx-ui-color-bg);
|
|
53
|
+
--flx-ui-paper-control-hover: var(--flx-ui-gray-2);
|
|
54
|
+
--flx-ui-paper-track: var(--flx-ui-gray-2);
|
|
55
|
+
--flx-ui-paper-floating: var(--flx-ui-color-bg);
|
|
56
|
+
--flx-ui-paper-item-hover: color-mix(in srgb, var(--flx-ui-gray-12) 6%, transparent);
|
|
57
|
+
|
|
58
|
+
/* Rings from the foreground, so they flip with the theme. */
|
|
59
|
+
--flx-ui-paper-ring: color-mix(in srgb, var(--flx-ui-gray-12) 10%, transparent);
|
|
60
|
+
--flx-ui-paper-ring-strong: color-mix(in srgb, var(--flx-ui-gray-12) 16%, transparent);
|
|
61
|
+
--flx-ui-paper-ring-field: color-mix(in srgb, var(--flx-ui-gray-12) 18%, transparent);
|
|
62
|
+
--flx-ui-paper-contact: rgb(24 22 18 / 0.05);
|
|
63
|
+
--flx-ui-paper-ambient: rgb(24 22 18 / 0.08);
|
|
64
|
+
--flx-ui-paper-highlight: transparent;
|
|
65
|
+
|
|
66
|
+
/* The four families. */
|
|
67
|
+
--flx-ui-paper-field-shadow: inset 0 0 0 0.5px var(--flx-ui-paper-ring-field),
|
|
68
|
+
inset 0 1px 2px var(--flx-ui-paper-contact);
|
|
69
|
+
--flx-ui-paper-control-shadow: inset 0 1px 0 var(--flx-ui-paper-highlight),
|
|
70
|
+
0 0 0 0.5px var(--flx-ui-paper-ring-strong), 0 1px 1px var(--flx-ui-paper-contact);
|
|
71
|
+
--flx-ui-paper-card-shadow: inset 0 1px 0 var(--flx-ui-paper-highlight),
|
|
72
|
+
0 0 0 0.5px var(--flx-ui-paper-ring), 0 1px 2px var(--flx-ui-paper-contact),
|
|
73
|
+
0 4px 16px -8px var(--flx-ui-paper-ambient);
|
|
74
|
+
--flx-ui-paper-panel-shadow: inset 0 1px 0 var(--flx-ui-paper-highlight),
|
|
75
|
+
0 0 0 0.5px var(--flx-ui-paper-ring), 0 1px 2px var(--flx-ui-paper-contact),
|
|
76
|
+
0 10px 32px var(--flx-ui-paper-ambient);
|
|
77
|
+
--flx-ui-paper-menu-shadow: inset 0 1px 0 var(--flx-ui-paper-highlight),
|
|
78
|
+
0 0 0 0.5px var(--flx-ui-paper-ring-strong), 0 4px 8px var(--flx-ui-paper-contact),
|
|
79
|
+
0 16px 40px rgb(24 22 18 / 0.12);
|
|
80
|
+
--flx-ui-paper-action-highlight: rgb(255 255 255 / 0.16);
|
|
81
|
+
--flx-ui-paper-dialog-shadow: inset 0 1px 0 var(--flx-ui-paper-highlight),
|
|
82
|
+
0 0 0 0.5px var(--flx-ui-paper-ring-strong), 0 8px 16px rgb(24 22 18 / 0.08),
|
|
83
|
+
0 32px 80px rgb(24 22 18 / 0.2);
|
|
84
|
+
|
|
85
|
+
/* fluixi-ui's elevation scale, mapped onto the families. */
|
|
86
|
+
--flx-ui-shadow-sm: var(--flx-ui-paper-control-shadow);
|
|
87
|
+
--flx-ui-shadow-md: var(--flx-ui-paper-card-shadow);
|
|
88
|
+
--flx-ui-shadow-lg: var(--flx-ui-paper-menu-shadow);
|
|
89
|
+
--flx-ui-shadow-xl: var(--flx-ui-paper-dialog-shadow);
|
|
90
|
+
|
|
91
|
+
font-family: var(--flx-ui-font-family-sans);
|
|
92
|
+
font-size: var(--flx-ui-font-size-md);
|
|
93
|
+
background: var(--flx-ui-paper-canvas);
|
|
94
|
+
color: var(--flx-ui-color-fg);
|
|
95
|
+
-webkit-font-smoothing: antialiased;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Dark: shadows go unseen, so surfaces lift with a lighter ring and a top highlight. */
|
|
99
|
+
[data-ui='paper']:where([data-theme='dark']),
|
|
100
|
+
:where([data-theme='dark']) [data-ui='paper'] {
|
|
101
|
+
--flx-ui-paper-canvas: var(--flx-ui-gray-1);
|
|
102
|
+
--flx-ui-paper-sheet: var(--flx-ui-gray-2);
|
|
103
|
+
--flx-ui-paper-sunken: var(--flx-ui-gray-3);
|
|
104
|
+
--flx-ui-paper-field: var(--flx-ui-gray-1);
|
|
105
|
+
--flx-ui-paper-control: var(--flx-ui-gray-4);
|
|
106
|
+
--flx-ui-paper-control-hover: var(--flx-ui-gray-5);
|
|
107
|
+
--flx-ui-paper-track: var(--flx-ui-gray-1);
|
|
108
|
+
--flx-ui-paper-floating: var(--flx-ui-gray-3);
|
|
109
|
+
--flx-ui-paper-item-hover: color-mix(in srgb, var(--flx-ui-gray-12) 8%, transparent);
|
|
110
|
+
--flx-ui-paper-contact: rgb(0 0 0 / 0.4);
|
|
111
|
+
--flx-ui-paper-ambient: rgb(0 0 0 / 0.5);
|
|
112
|
+
--flx-ui-paper-highlight: rgb(255 255 255 / 0.04);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Display face for page and record titles. */
|
|
116
|
+
[data-ui='paper'] :where(h1, [data-display]) {
|
|
117
|
+
font-family: var(--flx-ui-paper-font-display);
|
|
118
|
+
font-weight: 700;
|
|
119
|
+
letter-spacing: -0.035em;
|
|
120
|
+
line-height: 1.1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ---- Card: the resting family ------------------------------------------------ */
|
|
124
|
+
[data-ui='paper'] :where([data-card]) {
|
|
125
|
+
background: var(--flx-ui-paper-sheet);
|
|
126
|
+
border: none;
|
|
127
|
+
border-radius: var(--flx-ui-radius-lg);
|
|
128
|
+
box-shadow: var(--flx-ui-paper-card-shadow);
|
|
129
|
+
}
|
|
130
|
+
[data-ui='paper'] :where([data-card-header]) {
|
|
131
|
+
gap: 2px;
|
|
132
|
+
padding: 16px 20px 0;
|
|
133
|
+
}
|
|
134
|
+
[data-ui='paper'] :where([data-card-title]) {
|
|
135
|
+
font-size: var(--flx-ui-font-size-lg);
|
|
136
|
+
font-weight: 600;
|
|
137
|
+
letter-spacing: -0.005em;
|
|
138
|
+
}
|
|
139
|
+
[data-ui='paper'] :where([data-card-description]) {
|
|
140
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
141
|
+
}
|
|
142
|
+
[data-ui='paper'] :where([data-card-body]) {
|
|
143
|
+
padding: 14px 20px 18px;
|
|
144
|
+
font-size: var(--flx-ui-font-size-md);
|
|
145
|
+
}
|
|
146
|
+
[data-ui='paper'] :where([data-card-footer]) {
|
|
147
|
+
padding: 12px 20px;
|
|
148
|
+
border-top: none;
|
|
149
|
+
box-shadow: inset 0 0.5px 0 var(--flx-ui-paper-ring);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* ---- Surface: elevation 0 flat, 1 resting, 2 panel, 3 menu, 4 dialog ------------- */
|
|
153
|
+
[data-ui='paper'] :where([data-surface]) {
|
|
154
|
+
background: var(--flx-ui-paper-sheet);
|
|
155
|
+
border: none;
|
|
156
|
+
}
|
|
157
|
+
[data-ui='paper'] :where([data-surface][data-elevation='0']) { box-shadow: none; }
|
|
158
|
+
[data-ui='paper'] :where([data-surface][data-elevation='0'][data-bordered]) {
|
|
159
|
+
box-shadow: 0 0 0 0.5px var(--flx-ui-paper-ring);
|
|
160
|
+
}
|
|
161
|
+
[data-ui='paper'] :where([data-surface][data-elevation='1']) { box-shadow: var(--flx-ui-paper-card-shadow); }
|
|
162
|
+
[data-ui='paper'] :where([data-surface][data-elevation='2']) { box-shadow: var(--flx-ui-paper-panel-shadow); }
|
|
163
|
+
[data-ui='paper'] :where([data-surface][data-elevation='3']) { box-shadow: var(--flx-ui-paper-menu-shadow); }
|
|
164
|
+
[data-ui='paper'] :where([data-surface][data-elevation='4']) { box-shadow: var(--flx-ui-paper-dialog-shadow); }
|
|
165
|
+
|
|
166
|
+
/* ---- Buttons: the control family -------------------------------------------- */
|
|
167
|
+
/* Rings replace the border; pressing settles the button half a pixel. Focus is an
|
|
168
|
+
outline, offset 2px, so it never replaces the construction shadow. */
|
|
169
|
+
[data-ui='paper'] :where([data-variant]:not([data-badge])) {
|
|
170
|
+
border: none;
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
transition:
|
|
173
|
+
background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
174
|
+
box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
175
|
+
transform var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
176
|
+
}
|
|
177
|
+
[data-ui='paper'] :where([data-variant]:not([data-badge])[data-pressed]:not([data-disabled])) {
|
|
178
|
+
transform: translateY(0.5px);
|
|
179
|
+
}
|
|
180
|
+
[data-ui='paper'] :where([data-variant]:not([data-badge]):focus-visible) {
|
|
181
|
+
outline: 2px solid var(--flx-ui-color-focus-ring);
|
|
182
|
+
outline-offset: 2px;
|
|
183
|
+
}
|
|
184
|
+
/* A filled action: top highlight, a ring darker than its fill, contact below. Built
|
|
185
|
+
here, not as a root token, so a data-tone on the button recolours the ring. */
|
|
186
|
+
[data-ui='paper'] :where([data-variant='solid']:not([data-badge])) {
|
|
187
|
+
box-shadow: inset 0 1px 0 var(--flx-ui-paper-action-highlight),
|
|
188
|
+
0 0 0 0.5px color-mix(in srgb, var(--flx-ui-color-primary) 70%, #000),
|
|
189
|
+
0 1px 2px var(--flx-ui-paper-contact);
|
|
190
|
+
}
|
|
191
|
+
[data-ui='paper'] :where([data-variant='outline']:not([data-badge])) {
|
|
192
|
+
background: var(--flx-ui-paper-control);
|
|
193
|
+
box-shadow: var(--flx-ui-paper-control-shadow);
|
|
194
|
+
}
|
|
195
|
+
[data-ui='paper'] :where([data-variant='outline']:not([data-badge]):hover:not([data-disabled])) {
|
|
196
|
+
background: var(--flx-ui-paper-control-hover);
|
|
197
|
+
}
|
|
198
|
+
[data-ui='paper'] :where([data-variant='soft']:not([data-badge])) {
|
|
199
|
+
box-shadow: none;
|
|
200
|
+
}
|
|
201
|
+
[data-ui='paper'] :where([data-variant='ghost']:not([data-badge])) {
|
|
202
|
+
box-shadow: none;
|
|
203
|
+
}
|
|
204
|
+
[data-ui='paper'] :where([data-variant='ghost']:not([data-badge]):hover:not([data-disabled])) {
|
|
205
|
+
background: color-mix(in srgb, var(--flx-ui-gray-12) 6%, transparent);
|
|
206
|
+
}
|
|
207
|
+
[data-ui='paper'] :where([data-variant]:not([data-badge])[data-disabled]) {
|
|
208
|
+
box-shadow: none;
|
|
209
|
+
}
|
|
210
|
+
[data-ui='paper'] :where([data-variant='outline']:not([data-badge])[data-disabled]) {
|
|
211
|
+
background: transparent;
|
|
212
|
+
box-shadow: 0 0 0 0.5px var(--flx-ui-paper-ring);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Icon button: glyphs a step smaller than the default, to sit in a 36px control. */
|
|
216
|
+
[data-ui='paper'] :where([data-icon-button] svg) { width: 18px; height: 18px; }
|
|
217
|
+
[data-ui='paper'] :where([data-icon-button][data-size='sm'] svg) { width: 16px; height: 16px; }
|
|
218
|
+
[data-ui='paper'] :where([data-icon-button][data-size='lg'] svg) { width: 20px; height: 20px; }
|
|
219
|
+
|
|
220
|
+
/* ---- Toggle -------------------------------------------------------------------- */
|
|
221
|
+
[data-ui='paper'] :where([aria-pressed]) {
|
|
222
|
+
border: none;
|
|
223
|
+
background: var(--flx-ui-paper-control);
|
|
224
|
+
box-shadow: var(--flx-ui-paper-control-shadow);
|
|
225
|
+
font-weight: 500;
|
|
226
|
+
}
|
|
227
|
+
[data-ui='paper'] :where([aria-pressed]:hover:not([data-disabled])) {
|
|
228
|
+
background: var(--flx-ui-paper-control-hover);
|
|
229
|
+
}
|
|
230
|
+
[data-ui='paper'] :where([aria-pressed][data-state='on']) {
|
|
231
|
+
background: var(--flx-ui-color-primary-subtle);
|
|
232
|
+
box-shadow: 0 0 0 0.5px var(--flx-ui-color-primary), 0 1px 1px var(--flx-ui-paper-contact);
|
|
233
|
+
}
|
|
234
|
+
[data-ui='paper'] :where([aria-pressed]:focus-visible) {
|
|
235
|
+
outline: 2px solid var(--flx-ui-color-focus-ring);
|
|
236
|
+
outline-offset: 2px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Toggle group: a recessed track, the chosen item raised out of it. Items keep the
|
|
240
|
+
track's radius minus its 2px padding, and the track keeps the control height. */
|
|
241
|
+
[data-ui='paper'] :where([role='group'][data-orientation]:has(> [aria-pressed])) {
|
|
242
|
+
gap: 2px;
|
|
243
|
+
padding: 2px;
|
|
244
|
+
border-radius: var(--flx-ui-radius-md);
|
|
245
|
+
background: var(--flx-ui-paper-track);
|
|
246
|
+
box-shadow: inset 0 0 0 0.5px var(--flx-ui-paper-ring);
|
|
247
|
+
}
|
|
248
|
+
[data-ui='paper'] :where([role='group'][data-orientation] > [aria-pressed]) {
|
|
249
|
+
height: calc(var(--flx-ui-size-md) - 4px);
|
|
250
|
+
margin: 0;
|
|
251
|
+
border-radius: calc(var(--flx-ui-radius-md) - 2px);
|
|
252
|
+
background: transparent;
|
|
253
|
+
box-shadow: none;
|
|
254
|
+
color: var(--flx-ui-color-fg-muted);
|
|
255
|
+
}
|
|
256
|
+
[data-ui='paper'] :where([role='group'][data-orientation] > [aria-pressed]:hover:not([data-disabled])) {
|
|
257
|
+
background: transparent;
|
|
258
|
+
color: var(--flx-ui-color-fg);
|
|
259
|
+
}
|
|
260
|
+
[data-ui='paper'] :where([role='group'][data-orientation] > [aria-pressed][data-state='on']) {
|
|
261
|
+
background: var(--flx-ui-paper-control);
|
|
262
|
+
box-shadow: var(--flx-ui-paper-control-shadow);
|
|
263
|
+
color: var(--flx-ui-color-fg);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* ---- Fields: input, input group, select trigger, combobox --------------------- */
|
|
267
|
+
/* Recessed: no border, an inset ring and shadow. Hover deepens the ring; focus is the
|
|
268
|
+
same offset outline as the controls. Outlined and filled variants keep their own look. */
|
|
269
|
+
[data-ui='paper'] :where([data-input]:not([data-field-variant]),
|
|
270
|
+
[data-input-group]:not([data-field-variant]),
|
|
271
|
+
button[role='combobox']:not([data-field-variant]),
|
|
272
|
+
[data-combobox-control]:not([data-field-variant])) {
|
|
273
|
+
border: none;
|
|
274
|
+
min-height: var(--flx-ui-size-md);
|
|
275
|
+
border-radius: var(--flx-ui-radius-md);
|
|
276
|
+
background: var(--flx-ui-paper-field);
|
|
277
|
+
box-shadow: var(--flx-ui-paper-field-shadow);
|
|
278
|
+
}
|
|
279
|
+
[data-ui='paper'] :where(button[role='combobox']:not([data-field-variant])) {
|
|
280
|
+
height: var(--flx-ui-size-md);
|
|
281
|
+
}
|
|
282
|
+
[data-ui='paper'] :where([data-input]:not([data-field-variant]):hover,
|
|
283
|
+
[data-input-group]:not([data-field-variant]):hover,
|
|
284
|
+
button[role='combobox']:not([data-field-variant]):hover,
|
|
285
|
+
[data-combobox-control]:not([data-field-variant]):hover) {
|
|
286
|
+
box-shadow: inset 0 0 0 0.5px var(--flx-ui-gray-8), inset 0 1px 2px var(--flx-ui-paper-contact);
|
|
287
|
+
}
|
|
288
|
+
[data-ui='paper'] :where([data-input]:not([data-field-variant]):focus-visible,
|
|
289
|
+
[data-input-group]:not([data-field-variant]):focus-within,
|
|
290
|
+
button[role='combobox']:not([data-field-variant]):focus-visible,
|
|
291
|
+
[data-combobox-control]:not([data-field-variant]):focus-within) {
|
|
292
|
+
outline: 2px solid var(--flx-ui-color-focus-ring);
|
|
293
|
+
outline-offset: 2px;
|
|
294
|
+
box-shadow: var(--flx-ui-paper-field-shadow);
|
|
295
|
+
}
|
|
296
|
+
[data-ui='paper'] :where([data-input][data-invalid]:not([data-field-variant]),
|
|
297
|
+
[data-input-group][data-invalid]:not([data-field-variant])) {
|
|
298
|
+
box-shadow: inset 0 0 0 1px var(--flx-ui-color-danger), inset 0 1px 2px var(--flx-ui-paper-contact);
|
|
299
|
+
}
|
|
300
|
+
/* The combobox's toggle sits inside the field, a square two pixels short of it. */
|
|
301
|
+
[data-ui='paper'] :where([data-combobox-control] > button) {
|
|
302
|
+
align-self: stretch;
|
|
303
|
+
margin: 3px 0;
|
|
304
|
+
padding: 0 6px;
|
|
305
|
+
border-radius: calc(var(--flx-ui-radius-md) - 3px);
|
|
306
|
+
}
|
|
307
|
+
[data-ui='paper'] :where([data-combobox-control] > button:hover) {
|
|
308
|
+
background: var(--flx-ui-paper-item-hover);
|
|
309
|
+
color: var(--flx-ui-color-fg);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* ---- Popup triggers ------------------------------------------------------------ */
|
|
313
|
+
/* DropdownMenuTrigger and PopoverTrigger render a bare button; given no variant, it
|
|
314
|
+
takes the control recipe. A trigger with a variant is already a Button. */
|
|
315
|
+
[data-ui='paper'] :where(button[aria-haspopup='menu']:not([data-variant]):not([role='menuitem']),
|
|
316
|
+
button[aria-haspopup='dialog']:not([data-variant])) {
|
|
317
|
+
box-sizing: border-box;
|
|
318
|
+
display: inline-flex;
|
|
319
|
+
align-items: center;
|
|
320
|
+
gap: 6px;
|
|
321
|
+
height: var(--flx-ui-size-md);
|
|
322
|
+
padding: 0 12px;
|
|
323
|
+
font: inherit;
|
|
324
|
+
font-weight: 500;
|
|
325
|
+
color: var(--flx-ui-color-fg);
|
|
326
|
+
background: var(--flx-ui-paper-control);
|
|
327
|
+
border: none;
|
|
328
|
+
border-radius: var(--flx-ui-radius-md);
|
|
329
|
+
box-shadow: var(--flx-ui-paper-control-shadow);
|
|
330
|
+
cursor: pointer;
|
|
331
|
+
transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
332
|
+
}
|
|
333
|
+
[data-ui='paper'] :where(button[aria-haspopup='menu']:not([data-variant]):not([role='menuitem']),
|
|
334
|
+
button[aria-haspopup='dialog']:not([data-variant])):where(:hover, [data-state='open']) {
|
|
335
|
+
background: var(--flx-ui-paper-control-hover);
|
|
336
|
+
}
|
|
337
|
+
[data-ui='paper'] :where(button[aria-haspopup='menu']:not([data-variant]):not([role='menuitem']),
|
|
338
|
+
button[aria-haspopup='dialog']:not([data-variant])):focus-visible {
|
|
339
|
+
outline: 2px solid var(--flx-ui-color-focus-ring);
|
|
340
|
+
outline-offset: 2px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* ---- Floating: menus, select and combobox popups, popovers, dialogs ----------- */
|
|
344
|
+
/* Menus and popups pad 4px, so their 14px corner holds items at the 10px control
|
|
345
|
+
radius. Hover and keyboard focus are a neutral tint; the accent marks selection. */
|
|
346
|
+
[data-ui='paper'] :where([role='menu'], [role='listbox'][data-state='open']) {
|
|
347
|
+
padding: 4px;
|
|
348
|
+
border: none;
|
|
349
|
+
border-radius: calc(var(--flx-ui-radius-md) + 4px);
|
|
350
|
+
background: var(--flx-ui-paper-floating);
|
|
351
|
+
box-shadow: var(--flx-ui-paper-menu-shadow);
|
|
352
|
+
}
|
|
353
|
+
[data-ui='paper'] :where([role='menuitem'], [role='option']) {
|
|
354
|
+
min-height: calc(var(--flx-ui-size-md) - 4px);
|
|
355
|
+
padding: 0 10px;
|
|
356
|
+
border-radius: var(--flx-ui-radius-md);
|
|
357
|
+
font-size: var(--flx-ui-font-size-md);
|
|
358
|
+
}
|
|
359
|
+
[data-ui='paper'] :where([role='menu'] [role='menuitem'], [role='listbox'] [role='option']):where(:hover, :focus, [data-active]) {
|
|
360
|
+
background: var(--flx-ui-paper-item-hover);
|
|
361
|
+
color: var(--flx-ui-color-fg);
|
|
362
|
+
}
|
|
363
|
+
[data-ui='paper'] :where([role='menuitem'][aria-haspopup='menu'][data-state='open']) {
|
|
364
|
+
background: var(--flx-ui-paper-item-hover);
|
|
365
|
+
color: var(--flx-ui-color-fg);
|
|
366
|
+
}
|
|
367
|
+
[data-ui='paper'] :where([role='menu'] [role='separator']) {
|
|
368
|
+
height: 0;
|
|
369
|
+
margin: 4px 6px;
|
|
370
|
+
background: none;
|
|
371
|
+
box-shadow: 0 0 0 0.25px var(--flx-ui-paper-ring-strong);
|
|
372
|
+
}
|
|
373
|
+
[data-ui='paper'] :where([data-menu-label], [data-listbox-group-label]) {
|
|
374
|
+
padding: 8px 10px 4px;
|
|
375
|
+
letter-spacing: 0.02em;
|
|
376
|
+
}
|
|
377
|
+
[data-ui='paper'] :where([data-listbox-search], [data-select-search]) {
|
|
378
|
+
min-height: calc(var(--flx-ui-size-md) - 4px);
|
|
379
|
+
margin-bottom: 4px;
|
|
380
|
+
border: none;
|
|
381
|
+
border-radius: var(--flx-ui-radius-md);
|
|
382
|
+
background: var(--flx-ui-paper-field);
|
|
383
|
+
box-shadow: var(--flx-ui-paper-field-shadow);
|
|
384
|
+
}
|
|
385
|
+
[data-ui='paper'] :where([role='dialog'][data-placement]) {
|
|
386
|
+
border: none;
|
|
387
|
+
border-radius: var(--flx-ui-radius-lg);
|
|
388
|
+
background: var(--flx-ui-paper-floating);
|
|
389
|
+
box-shadow: var(--flx-ui-paper-menu-shadow);
|
|
390
|
+
}
|
|
391
|
+
[data-ui='paper'] :where([role='dialog']:not([data-placement]), [role='alertdialog']) {
|
|
392
|
+
border: none;
|
|
393
|
+
border-radius: var(--flx-ui-radius-xl);
|
|
394
|
+
background: var(--flx-ui-paper-floating);
|
|
395
|
+
box-shadow: var(--flx-ui-paper-dialog-shadow);
|
|
396
|
+
}
|
|
397
|
+
}
|
package/paper.css.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const href = new URL('./paper.css', import.meta.url).href;
|
|
2
|
+
if (typeof document !== 'undefined' && !document.querySelector(`link[data-flx-ui="${href}"]`)) {
|
|
3
|
+
const link = document.createElement('link');
|
|
4
|
+
link.rel = 'stylesheet';
|
|
5
|
+
link.href = href;
|
|
6
|
+
link.dataset.flxUi = href;
|
|
7
|
+
document.head.appendChild(link);
|
|
8
|
+
}
|