@fluixi-ui/tokens 0.3.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 +106 -0
- package/dist/css/accordion.css +7 -0
- package/dist/css/alert.css +42 -0
- package/dist/css/avatar.css +36 -0
- package/dist/css/badge.css +12 -0
- package/dist/css/base.css +10 -0
- package/dist/css/bulk-editor.css +478 -0
- package/dist/css/button.css +92 -0
- package/dist/css/calendar.css +140 -0
- package/dist/css/checkbox-radio.css +66 -0
- package/dist/css/command.css +90 -0
- package/dist/css/date-field.css +58 -0
- package/dist/css/date-picker.css +81 -0
- package/dist/css/field.css +19 -0
- package/dist/css/hover-card.css +16 -0
- package/dist/css/input.css +85 -0
- package/dist/css/kbd.css +20 -0
- package/dist/css/menubar.css +27 -0
- package/dist/css/navigation-menu.css +144 -0
- package/dist/css/number-field.css +83 -0
- package/dist/css/parts/listbox.css +111 -0
- package/dist/css/parts/menu.css +69 -0
- package/dist/css/parts/motion.css +34 -0
- package/dist/css/parts/overlays.css +36 -0
- package/dist/css/progress.css +40 -0
- package/dist/css/select-combobox.css +76 -0
- package/dist/css/separator.css +15 -0
- package/dist/css/skeleton.css +22 -0
- package/dist/css/slider.css +71 -0
- package/dist/css/spinner.css +17 -0
- package/dist/css/switch.css +33 -0
- package/dist/css/table.css +173 -0
- package/dist/css/tabs.css +32 -0
- package/dist/css/toast.css +154 -0
- package/dist/css/toggle.css +74 -0
- package/dist/css/toolbar.css +48 -0
- package/dist/css/tooltip.css +13 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/css/index.d.ts +20 -0
- package/dist/lib/css/index.d.ts.map +1 -0
- package/dist/lib/css/index.js +135 -0
- package/dist/lib/reference/colors.d.ts +24 -0
- package/dist/lib/reference/colors.d.ts.map +1 -0
- package/dist/lib/reference/colors.js +150 -0
- package/dist/lib/reference/index.d.ts +302 -0
- package/dist/lib/reference/index.d.ts.map +1 -0
- package/dist/lib/reference/index.js +147 -0
- package/dist/lib/theme/index.d.ts +32 -0
- package/dist/lib/theme/index.d.ts.map +1 -0
- package/dist/lib/theme/index.js +87 -0
- package/dist/lib/vars/index.d.ts +131 -0
- package/dist/lib/vars/index.d.ts.map +1 -0
- package/dist/lib/vars/index.js +40 -0
- package/dist/preset.css +47 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Table
|
|
4
|
+
* ------------------------------------------------------------------------- */
|
|
5
|
+
:where([data-table]) {
|
|
6
|
+
width: 100%;
|
|
7
|
+
border-collapse: collapse;
|
|
8
|
+
border-spacing: 0;
|
|
9
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
10
|
+
color: var(--flx-ui-color-fg);
|
|
11
|
+
/* Hairline frame; individual row rules draw the horizontal grid. */
|
|
12
|
+
border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
13
|
+
border-radius: var(--flx-ui-radius-lg);
|
|
14
|
+
/* border-radius needs the overflow clipped since cells are square. */
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:where([data-table-head]) {
|
|
19
|
+
position: relative;
|
|
20
|
+
padding: var(--flx-ui-space-2) var(--flx-ui-space-3);
|
|
21
|
+
text-align: start;
|
|
22
|
+
vertical-align: bottom;
|
|
23
|
+
font-weight: var(--flx-ui-font-weight-semibold);
|
|
24
|
+
font-size: var(--flx-ui-font-size-xs);
|
|
25
|
+
letter-spacing: var(--flx-ui-letter-spacing-wide);
|
|
26
|
+
text-transform: uppercase;
|
|
27
|
+
color: var(--flx-ui-color-fg-muted);
|
|
28
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
29
|
+
border-bottom: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
:where([data-table-cell]) {
|
|
34
|
+
padding: var(--flx-ui-space-2) var(--flx-ui-space-3);
|
|
35
|
+
text-align: start;
|
|
36
|
+
vertical-align: middle;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Row separators; the last row's is dropped so it sits flush to the frame. */
|
|
40
|
+
:where([data-table-body] > [data-table-row]) {
|
|
41
|
+
border-top: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
42
|
+
}
|
|
43
|
+
:where([data-table-body] > [data-table-row]:first-child) {
|
|
44
|
+
border-top: none;
|
|
45
|
+
}
|
|
46
|
+
:where([data-table-body] > [data-table-row]:hover) {
|
|
47
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
48
|
+
}
|
|
49
|
+
:where([data-table-row][data-selected]) {
|
|
50
|
+
background: var(--flx-ui-color-primary-subtle);
|
|
51
|
+
}
|
|
52
|
+
:where([data-table-row][data-selected]:hover) {
|
|
53
|
+
background: var(--flx-ui-color-primary-subtle);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Sortable header: a full-bleed button so the whole cell is the hit target. */
|
|
57
|
+
:where([data-table-sort-button]) {
|
|
58
|
+
display: inline-flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: var(--flx-ui-space-1);
|
|
61
|
+
width: 100%;
|
|
62
|
+
margin: calc(-1 * var(--flx-ui-space-2)) calc(-1 * var(--flx-ui-space-3));
|
|
63
|
+
padding: var(--flx-ui-space-2) var(--flx-ui-space-3);
|
|
64
|
+
font: inherit;
|
|
65
|
+
letter-spacing: inherit;
|
|
66
|
+
text-transform: inherit;
|
|
67
|
+
color: inherit;
|
|
68
|
+
background: none;
|
|
69
|
+
border: none;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
border-radius: var(--flx-ui-radius-sm);
|
|
72
|
+
user-select: none;
|
|
73
|
+
}
|
|
74
|
+
:where([data-table-sort-button]:hover) {
|
|
75
|
+
color: var(--flx-ui-color-fg);
|
|
76
|
+
}
|
|
77
|
+
:where([data-table-sort-button]:focus-visible) {
|
|
78
|
+
outline: none;
|
|
79
|
+
box-shadow: var(--flx-ui-ctl-ring);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Neutral sort chevron; brightens and flips when the column is sorted. */
|
|
83
|
+
:where([data-table-sort-indicator]) {
|
|
84
|
+
width: 0.6em;
|
|
85
|
+
height: 0.6em;
|
|
86
|
+
flex: none;
|
|
87
|
+
opacity: 0;
|
|
88
|
+
transition:
|
|
89
|
+
opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
90
|
+
transform var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
91
|
+
background: currentcolor;
|
|
92
|
+
clip-path: polygon(50% 75%, 0 25%, 100% 25%);
|
|
93
|
+
}
|
|
94
|
+
:where([data-table-head][data-sortable]:hover [data-table-sort-indicator]) {
|
|
95
|
+
opacity: 0.4;
|
|
96
|
+
}
|
|
97
|
+
:where([data-table-sort-indicator][data-direction='asc']) {
|
|
98
|
+
opacity: 1;
|
|
99
|
+
transform: rotate(180deg);
|
|
100
|
+
}
|
|
101
|
+
:where([data-table-sort-indicator][data-direction='desc']) {
|
|
102
|
+
opacity: 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
:where([data-table-footer]) {
|
|
106
|
+
color: var(--flx-ui-color-fg-muted);
|
|
107
|
+
border-top: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
108
|
+
}
|
|
109
|
+
:where([data-table-footer] [data-table-cell]) {
|
|
110
|
+
padding: var(--flx-ui-space-2) var(--flx-ui-space-3);
|
|
111
|
+
font-size: var(--flx-ui-font-size-xs);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Header cell whose content is a right-click column menu trigger. */
|
|
115
|
+
:where([data-table-column-menu-trigger]) {
|
|
116
|
+
display: inline-flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: var(--flx-ui-space-1);
|
|
119
|
+
width: 100%;
|
|
120
|
+
}
|
|
121
|
+
:where([data-table-column-menu-trigger][data-sortable]) {
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
}
|
|
124
|
+
:where([data-table-head]:hover [data-table-column-menu-trigger][data-sortable] [data-table-sort-indicator]) {
|
|
125
|
+
opacity: 0.4;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Leading check in the view menu; shown only for a currently-visible column. */
|
|
129
|
+
:where([data-table-menu-check]) {
|
|
130
|
+
display: inline-block;
|
|
131
|
+
width: 1em;
|
|
132
|
+
opacity: 0;
|
|
133
|
+
}
|
|
134
|
+
:where([data-table-menu-check][data-checked]) {
|
|
135
|
+
opacity: 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Drag-to-resize grip on the trailing edge of a header cell. */
|
|
139
|
+
:where([data-table-resize-handle]) {
|
|
140
|
+
position: absolute;
|
|
141
|
+
top: 0;
|
|
142
|
+
right: 0;
|
|
143
|
+
height: 100%;
|
|
144
|
+
width: 5px;
|
|
145
|
+
cursor: col-resize;
|
|
146
|
+
user-select: none;
|
|
147
|
+
touch-action: none;
|
|
148
|
+
background: var(--flx-ui-color-primary);
|
|
149
|
+
opacity: 0;
|
|
150
|
+
transition: opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
151
|
+
}
|
|
152
|
+
:where([data-table-head]:hover [data-table-resize-handle]) {
|
|
153
|
+
opacity: 0.4;
|
|
154
|
+
}
|
|
155
|
+
:where([data-table-resize-handle][data-resizing]) {
|
|
156
|
+
opacity: 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Pinned cells stay opaque while the center scrolls under them, with a hairline
|
|
160
|
+
* divider on the scrolling edge. */
|
|
161
|
+
:where([data-table-cell][data-pinned]) {
|
|
162
|
+
background: var(--flx-ui-color-bg);
|
|
163
|
+
}
|
|
164
|
+
:where([data-table-head][data-pinned]) {
|
|
165
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
166
|
+
}
|
|
167
|
+
:where([data-pinned='left']) {
|
|
168
|
+
box-shadow: inset -1px 0 0 var(--flx-ui-color-border);
|
|
169
|
+
}
|
|
170
|
+
:where([data-pinned='right']) {
|
|
171
|
+
box-shadow: inset 1px 0 0 var(--flx-ui-color-border);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Tabs
|
|
4
|
+
* ------------------------------------------------------------------------- */
|
|
5
|
+
:where([role='tablist']) {
|
|
6
|
+
display: flex;
|
|
7
|
+
gap: var(--flx-ui-space-1);
|
|
8
|
+
border-bottom: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
9
|
+
}
|
|
10
|
+
:where([role='tab']) {
|
|
11
|
+
border: none;
|
|
12
|
+
background: none;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
color: var(--flx-ui-color-fg-muted);
|
|
15
|
+
font: inherit;
|
|
16
|
+
font-weight: var(--flx-ui-font-weight-medium);
|
|
17
|
+
padding: var(--flx-ui-space-2) var(--flx-ui-space-3);
|
|
18
|
+
border-bottom: 2px solid transparent;
|
|
19
|
+
margin-bottom: -1px;
|
|
20
|
+
transition: color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
21
|
+
}
|
|
22
|
+
:where([role='tab']:hover) {
|
|
23
|
+
color: var(--flx-ui-color-fg);
|
|
24
|
+
}
|
|
25
|
+
:where([role='tab'][data-state='active']) {
|
|
26
|
+
color: var(--flx-ui-color-primary-active);
|
|
27
|
+
border-bottom-color: var(--flx-ui-color-primary);
|
|
28
|
+
}
|
|
29
|
+
:where([role='tabpanel']) {
|
|
30
|
+
padding: var(--flx-ui-space-4) 0;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Toast — fixed corner stacks of notifications.
|
|
4
|
+
* ------------------------------------------------------------------------- */
|
|
5
|
+
:where([data-toast-viewport]) {
|
|
6
|
+
position: fixed;
|
|
7
|
+
z-index: var(--flx-ui-z-toast);
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
gap: var(--flx-ui-space-3);
|
|
11
|
+
width: max-content;
|
|
12
|
+
max-width: min(380px, calc(100vw - var(--flx-ui-space-8)));
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: var(--flx-ui-space-4);
|
|
15
|
+
list-style: none;
|
|
16
|
+
/* The container spans a corner but only the toasts should catch pointer events. */
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
}
|
|
19
|
+
:where([data-toast-viewport][data-placement^='top']) {
|
|
20
|
+
top: 0;
|
|
21
|
+
flex-direction: column-reverse; /* newest nearest the top edge */
|
|
22
|
+
}
|
|
23
|
+
:where([data-toast-viewport][data-placement^='bottom']) {
|
|
24
|
+
bottom: 0;
|
|
25
|
+
}
|
|
26
|
+
:where([data-toast-viewport][data-placement$='left']) {
|
|
27
|
+
left: 0;
|
|
28
|
+
align-items: flex-start;
|
|
29
|
+
}
|
|
30
|
+
:where([data-toast-viewport][data-placement$='right']) {
|
|
31
|
+
right: 0;
|
|
32
|
+
align-items: flex-end;
|
|
33
|
+
}
|
|
34
|
+
:where([data-toast-viewport][data-placement$='center']) {
|
|
35
|
+
left: 50%;
|
|
36
|
+
transform: translateX(-50%);
|
|
37
|
+
align-items: center;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:where([data-toast]) {
|
|
41
|
+
pointer-events: auto;
|
|
42
|
+
box-sizing: border-box;
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: flex-start;
|
|
45
|
+
gap: var(--flx-ui-space-3);
|
|
46
|
+
width: 100%;
|
|
47
|
+
padding: var(--flx-ui-space-3) var(--flx-ui-space-4);
|
|
48
|
+
background: var(--flx-ui-color-bg);
|
|
49
|
+
color: var(--flx-ui-color-fg);
|
|
50
|
+
border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
51
|
+
/* A hairline accent stripe carries the type — restrained, no loud fills. */
|
|
52
|
+
border-left-width: 3px;
|
|
53
|
+
border-radius: var(--flx-ui-radius-lg);
|
|
54
|
+
box-shadow: var(--flx-ui-shadow-lg);
|
|
55
|
+
outline: none;
|
|
56
|
+
/* Swipe offset lives in a var the component drives during a drag. */
|
|
57
|
+
transform: translateX(var(--flx-ui-toast-swipe, 0px));
|
|
58
|
+
touch-action: pan-y;
|
|
59
|
+
animation: flx-ui-pop-in var(--flx-ui-duration-normal) var(--flx-ui-ease-emphasized);
|
|
60
|
+
}
|
|
61
|
+
/* Enter once: after the first play the row is latched so re-insertion (when
|
|
62
|
+
another toast is added) can't restart the animation. */
|
|
63
|
+
:where([data-toast][data-entered]) {
|
|
64
|
+
animation: none;
|
|
65
|
+
}
|
|
66
|
+
:where([data-toast]:focus-visible) {
|
|
67
|
+
box-shadow: var(--flx-ui-shadow-lg), var(--flx-ui-ctl-ring);
|
|
68
|
+
}
|
|
69
|
+
/* Live tracking while dragging; smooth settle-back or fling-out on release. */
|
|
70
|
+
:where([data-toast][data-swiping]) {
|
|
71
|
+
transition: none;
|
|
72
|
+
cursor: grab;
|
|
73
|
+
user-select: none;
|
|
74
|
+
}
|
|
75
|
+
:where([data-toast]:not([data-swiping])) {
|
|
76
|
+
transition:
|
|
77
|
+
transform var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
78
|
+
opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
79
|
+
}
|
|
80
|
+
:where([data-toast][data-swiped-out]) {
|
|
81
|
+
transition:
|
|
82
|
+
transform 150ms var(--flx-ui-ease-standard),
|
|
83
|
+
opacity 150ms var(--flx-ui-ease-standard);
|
|
84
|
+
}
|
|
85
|
+
:where([data-toast][data-type='success']) {
|
|
86
|
+
border-left-color: var(--flx-ui-color-success);
|
|
87
|
+
}
|
|
88
|
+
:where([data-toast][data-type='error']) {
|
|
89
|
+
border-left-color: var(--flx-ui-color-danger);
|
|
90
|
+
}
|
|
91
|
+
:where([data-toast][data-type='warning']) {
|
|
92
|
+
border-left-color: var(--flx-ui-color-warning);
|
|
93
|
+
}
|
|
94
|
+
:where([data-toast][data-type='info']),
|
|
95
|
+
:where([data-toast][data-type='loading']) {
|
|
96
|
+
border-left-color: var(--flx-ui-color-primary);
|
|
97
|
+
}
|
|
98
|
+
:where([data-toast-content]) {
|
|
99
|
+
flex: 1;
|
|
100
|
+
min-width: 0;
|
|
101
|
+
display: flex;
|
|
102
|
+
flex-direction: column;
|
|
103
|
+
gap: 2px;
|
|
104
|
+
}
|
|
105
|
+
:where([data-toast-title]) {
|
|
106
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
107
|
+
font-weight: var(--flx-ui-font-weight-medium);
|
|
108
|
+
line-height: var(--flx-ui-line-height-tight);
|
|
109
|
+
}
|
|
110
|
+
:where([data-toast-description]) {
|
|
111
|
+
font-size: var(--flx-ui-font-size-xs);
|
|
112
|
+
color: var(--flx-ui-color-fg-muted);
|
|
113
|
+
line-height: var(--flx-ui-line-height-normal);
|
|
114
|
+
}
|
|
115
|
+
:where([data-toast-close]) {
|
|
116
|
+
flex: none;
|
|
117
|
+
display: inline-flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: center;
|
|
120
|
+
width: 1.5em;
|
|
121
|
+
height: 1.5em;
|
|
122
|
+
margin: -2px -4px 0 0;
|
|
123
|
+
padding: 0;
|
|
124
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
125
|
+
color: var(--flx-ui-color-fg-muted);
|
|
126
|
+
background: transparent;
|
|
127
|
+
border: none;
|
|
128
|
+
border-radius: var(--flx-ui-radius-sm);
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
131
|
+
}
|
|
132
|
+
:where([data-toast-close]:hover) {
|
|
133
|
+
background: var(--flx-ui-color-bg-muted);
|
|
134
|
+
color: var(--flx-ui-color-fg);
|
|
135
|
+
}
|
|
136
|
+
:where([data-toast-action]) {
|
|
137
|
+
flex: none;
|
|
138
|
+
align-self: center;
|
|
139
|
+
padding: var(--flx-ui-space-1) var(--flx-ui-space-2);
|
|
140
|
+
font: inherit;
|
|
141
|
+
font-size: var(--flx-ui-font-size-xs);
|
|
142
|
+
font-weight: var(--flx-ui-font-weight-medium);
|
|
143
|
+
color: var(--flx-ui-color-primary-active);
|
|
144
|
+
background: var(--flx-ui-color-primary-subtle);
|
|
145
|
+
border: none;
|
|
146
|
+
border-radius: var(--flx-ui-radius-sm);
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
}
|
|
149
|
+
@media (prefers-reduced-motion: reduce) {
|
|
150
|
+
:where([data-toast]) {
|
|
151
|
+
animation: none;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Toggle & toggle group (segmented control)
|
|
4
|
+
* ------------------------------------------------------------------------- */
|
|
5
|
+
:where([aria-pressed]) {
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
gap: var(--flx-ui-space-2);
|
|
11
|
+
height: var(--flx-ui-size-md);
|
|
12
|
+
min-width: var(--flx-ui-size-md);
|
|
13
|
+
padding: 0 var(--flx-ui-space-3);
|
|
14
|
+
font: inherit;
|
|
15
|
+
font-weight: var(--flx-ui-font-weight-medium);
|
|
16
|
+
color: var(--flx-ui-color-fg-muted);
|
|
17
|
+
background: var(--flx-ui-color-bg);
|
|
18
|
+
border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border-strong);
|
|
19
|
+
border-radius: var(--flx-ui-radius-md);
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
transition:
|
|
22
|
+
background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
23
|
+
color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
24
|
+
border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
25
|
+
}
|
|
26
|
+
:where([aria-pressed]:hover:not([data-disabled])) {
|
|
27
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
28
|
+
color: var(--flx-ui-color-fg);
|
|
29
|
+
}
|
|
30
|
+
:where([aria-pressed][data-state='on']) {
|
|
31
|
+
position: relative;
|
|
32
|
+
z-index: 1;
|
|
33
|
+
background: var(--flx-ui-color-primary-subtle);
|
|
34
|
+
border-color: var(--flx-ui-color-primary);
|
|
35
|
+
color: var(--flx-ui-color-primary-active);
|
|
36
|
+
}
|
|
37
|
+
:where([aria-pressed][data-disabled]) {
|
|
38
|
+
opacity: var(--flx-ui-opacity-disabled);
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:where([role='group'][data-orientation='horizontal']) {
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
}
|
|
45
|
+
:where([role='group'][data-orientation='vertical']) {
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
}
|
|
49
|
+
:where([role='group'][data-orientation] > [aria-pressed]) {
|
|
50
|
+
border-radius: 0;
|
|
51
|
+
}
|
|
52
|
+
:where([role='group'][data-orientation='horizontal'] > [aria-pressed] + [aria-pressed]) {
|
|
53
|
+
margin-left: -1px;
|
|
54
|
+
}
|
|
55
|
+
:where([role='group'][data-orientation='vertical'] > [aria-pressed] + [aria-pressed]) {
|
|
56
|
+
margin-top: -1px;
|
|
57
|
+
}
|
|
58
|
+
:where([role='group'][data-orientation='horizontal'] > [aria-pressed]:first-child) {
|
|
59
|
+
border-top-left-radius: var(--flx-ui-radius-md);
|
|
60
|
+
border-bottom-left-radius: var(--flx-ui-radius-md);
|
|
61
|
+
}
|
|
62
|
+
:where([role='group'][data-orientation='horizontal'] > [aria-pressed]:last-child) {
|
|
63
|
+
border-top-right-radius: var(--flx-ui-radius-md);
|
|
64
|
+
border-bottom-right-radius: var(--flx-ui-radius-md);
|
|
65
|
+
}
|
|
66
|
+
:where([role='group'][data-orientation='vertical'] > [aria-pressed]:first-child) {
|
|
67
|
+
border-top-left-radius: var(--flx-ui-radius-md);
|
|
68
|
+
border-top-right-radius: var(--flx-ui-radius-md);
|
|
69
|
+
}
|
|
70
|
+
:where([role='group'][data-orientation='vertical'] > [aria-pressed]:last-child) {
|
|
71
|
+
border-bottom-left-radius: var(--flx-ui-radius-md);
|
|
72
|
+
border-bottom-right-radius: var(--flx-ui-radius-md);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Toolbar
|
|
4
|
+
* ------------------------------------------------------------------------- */
|
|
5
|
+
:where([role='toolbar']) {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
gap: var(--flx-ui-space-1);
|
|
9
|
+
padding: var(--flx-ui-space-1);
|
|
10
|
+
background: var(--flx-ui-color-bg);
|
|
11
|
+
border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
12
|
+
border-radius: var(--flx-ui-radius-lg);
|
|
13
|
+
}
|
|
14
|
+
:where([role='toolbar'][data-orientation='vertical']) {
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
align-items: stretch;
|
|
17
|
+
}
|
|
18
|
+
:where([role='toolbar'] > button:not([aria-pressed])) {
|
|
19
|
+
height: var(--flx-ui-size-sm);
|
|
20
|
+
min-width: var(--flx-ui-size-sm);
|
|
21
|
+
padding: 0 var(--flx-ui-space-3);
|
|
22
|
+
font: inherit;
|
|
23
|
+
font-weight: var(--flx-ui-font-weight-medium);
|
|
24
|
+
color: var(--flx-ui-color-fg-muted);
|
|
25
|
+
background: none;
|
|
26
|
+
border: none;
|
|
27
|
+
border-radius: var(--flx-ui-radius-sm);
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
30
|
+
}
|
|
31
|
+
:where([role='toolbar'] > button:not([aria-pressed]):hover) {
|
|
32
|
+
background: var(--flx-ui-color-bg-muted);
|
|
33
|
+
color: var(--flx-ui-color-fg);
|
|
34
|
+
}
|
|
35
|
+
:where([role='toolbar'] > [role='separator']) {
|
|
36
|
+
flex: none;
|
|
37
|
+
align-self: stretch;
|
|
38
|
+
width: 1px;
|
|
39
|
+
height: auto;
|
|
40
|
+
margin: var(--flx-ui-space-1) var(--flx-ui-space-1);
|
|
41
|
+
background: var(--flx-ui-color-border);
|
|
42
|
+
}
|
|
43
|
+
:where([role='toolbar'][data-orientation='vertical'] > [role='separator']) {
|
|
44
|
+
width: auto;
|
|
45
|
+
height: 1px;
|
|
46
|
+
margin: var(--flx-ui-space-1) var(--flx-ui-space-1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* Tooltip */
|
|
3
|
+
:where([role='tooltip']) {
|
|
4
|
+
padding: var(--flx-ui-space-1) var(--flx-ui-space-2);
|
|
5
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
6
|
+
color: var(--flx-ui-color-bg);
|
|
7
|
+
background: var(--flx-ui-color-fg);
|
|
8
|
+
border-radius: var(--flx-ui-radius-sm);
|
|
9
|
+
box-shadow: var(--flx-ui-shadow-md);
|
|
10
|
+
pointer-events: none;
|
|
11
|
+
animation: flx-ui-fade var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const TOKEN_PREFIX = "flx-ui";
|
|
2
|
+
/**
|
|
3
|
+
* Cascade layer order (low → high): tokens (vars) → components (the default
|
|
4
|
+
* "fluixi" look, `preset.css`) → skin (an alternate skin's overrides, e.g.
|
|
5
|
+
* `@fluixi-ui/skin-material`). Consumer CSS stays unlayered, so it always wins.
|
|
6
|
+
* Declared first in every stylesheet we emit so ordering is deterministic
|
|
7
|
+
* regardless of load order; redeclarations are no-ops.
|
|
8
|
+
*/
|
|
9
|
+
export declare const LAYER_ORDER = "@layer flx.tokens, flx.components, flx.skin;";
|
|
10
|
+
export declare function cssVar(name: string): string;
|
|
11
|
+
export declare function cssVarRef(name: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generate the full token stylesheet: static reference tokens and the light
|
|
14
|
+
* scales plus semantic aliases on `:root`, with dark and high-contrast themes
|
|
15
|
+
* overriding only the scales (and a few semantics). Dark also applies
|
|
16
|
+
* automatically via `prefers-color-scheme` when no explicit theme is set.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateTokensCss(): string;
|
|
19
|
+
export declare const tokensCss: string;
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/css/index.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,YAAY,WAAW,CAAC;AAErC;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,iDAAiD,CAAC;AAE1E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AA4FD;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAkC1C;AAED,eAAO,MAAM,SAAS,EAAE,MAA4B,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { staticTokens } from '../reference/index.js';
|
|
2
|
+
import { accentPresets, colorScales, semanticColors, semanticOverrides, } from '../reference/colors.js';
|
|
3
|
+
export const TOKEN_PREFIX = 'flx-ui';
|
|
4
|
+
/**
|
|
5
|
+
* Cascade layer order (low → high): tokens (vars) → components (the default
|
|
6
|
+
* "fluixi" look, `preset.css`) → skin (an alternate skin's overrides, e.g.
|
|
7
|
+
* `@fluixi-ui/skin-material`). Consumer CSS stays unlayered, so it always wins.
|
|
8
|
+
* Declared first in every stylesheet we emit so ordering is deterministic
|
|
9
|
+
* regardless of load order; redeclarations are no-ops.
|
|
10
|
+
*/
|
|
11
|
+
export const LAYER_ORDER = '@layer flx.tokens, flx.components, flx.skin;';
|
|
12
|
+
export function cssVar(name) {
|
|
13
|
+
return `--${TOKEN_PREFIX}-${name}`;
|
|
14
|
+
}
|
|
15
|
+
export function cssVarRef(name) {
|
|
16
|
+
return `var(${cssVar(name)})`;
|
|
17
|
+
}
|
|
18
|
+
function declarations(entries) {
|
|
19
|
+
return Object.entries(entries)
|
|
20
|
+
.map(([name, value]) => ` ${name}: ${value};`)
|
|
21
|
+
.join('\n');
|
|
22
|
+
}
|
|
23
|
+
function block(selector, entries) {
|
|
24
|
+
return `${selector} {\n${declarations(entries)}\n}`;
|
|
25
|
+
}
|
|
26
|
+
function staticVars() {
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const { prefix, values } of Object.values(staticTokens)) {
|
|
29
|
+
for (const [key, value] of Object.entries(values)) {
|
|
30
|
+
out[cssVar(`${prefix}-${key}`)] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function scaleVars(theme) {
|
|
36
|
+
const out = {};
|
|
37
|
+
for (const [name, value] of Object.entries(colorScales[theme])) {
|
|
38
|
+
out[cssVar(name)] = value;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function semanticVars() {
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const [name, value] of Object.entries(semanticColors)) {
|
|
45
|
+
out[cssVar(name)] = value;
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The accent-derived slice of the semantic layer (`color-primary`, focus ring,
|
|
51
|
+
* …). Custom properties resolve where they're declared, so a scoped `data-accent`
|
|
52
|
+
* subtree must re-declare these — otherwise the root's already-resolved color
|
|
53
|
+
* inherits down and the local `accent-*` override is ignored.
|
|
54
|
+
*/
|
|
55
|
+
function accentSemanticVars() {
|
|
56
|
+
const out = {};
|
|
57
|
+
for (const [name, value] of Object.entries(semanticColors)) {
|
|
58
|
+
if (value.includes('accent'))
|
|
59
|
+
out[cssVar(name)] = value;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function overrideVars(theme) {
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const [name, value] of Object.entries(semanticOverrides[theme] ?? {})) {
|
|
66
|
+
out[cssVar(name)] = value;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function accentVars(scale, fg) {
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const [step, value] of Object.entries(scale))
|
|
73
|
+
out[cssVar(`accent-${step}`)] = value;
|
|
74
|
+
if (fg)
|
|
75
|
+
out[cssVar('color-primary-fg')] = fg;
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Accent-preset blocks (`data-accent`). Each preset overrides only the accent
|
|
80
|
+
* scale, in every scheme. The scheme-qualified selectors (0,2,0) outrank the
|
|
81
|
+
* base `[data-theme]` blocks (0,1,0), so dark/light stay correct; the plain
|
|
82
|
+
* `[data-accent]` block trails `:root` in source order to win in light mode.
|
|
83
|
+
*/
|
|
84
|
+
function accentBlocks(name, preset) {
|
|
85
|
+
const sel = `[data-accent='${name}']`;
|
|
86
|
+
const light = accentVars(preset.light, preset.fg);
|
|
87
|
+
const dark = accentVars(preset.dark, preset.fg);
|
|
88
|
+
const autoDark = `@media (prefers-color-scheme: dark) {\n${block(`:root:not([data-theme])${sel}`, {
|
|
89
|
+
...dark,
|
|
90
|
+
...accentSemanticVars(),
|
|
91
|
+
})
|
|
92
|
+
.split('\n')
|
|
93
|
+
.map((line) => ` ${line}`)
|
|
94
|
+
.join('\n')}\n}`;
|
|
95
|
+
return [
|
|
96
|
+
block(`:root${sel}, [data-theme='light']${sel}`, { ...light, ...accentSemanticVars() }),
|
|
97
|
+
block(`[data-theme='dark']${sel}`, { ...dark, ...accentSemanticVars() }),
|
|
98
|
+
block(`[data-theme='high-contrast']${sel}`, { ...light, ...accentSemanticVars() }),
|
|
99
|
+
autoDark,
|
|
100
|
+
].join('\n\n');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Generate the full token stylesheet: static reference tokens and the light
|
|
104
|
+
* scales plus semantic aliases on `:root`, with dark and high-contrast themes
|
|
105
|
+
* overriding only the scales (and a few semantics). Dark also applies
|
|
106
|
+
* automatically via `prefers-color-scheme` when no explicit theme is set.
|
|
107
|
+
*/
|
|
108
|
+
export function generateTokensCss() {
|
|
109
|
+
const root = block(":root, [data-theme='light']", {
|
|
110
|
+
...staticVars(),
|
|
111
|
+
...scaleVars('light'),
|
|
112
|
+
...semanticVars(),
|
|
113
|
+
});
|
|
114
|
+
// Re-emit the semantic layer in each scope: custom properties resolve where
|
|
115
|
+
// they're declared, so a scoped `[data-theme]` subtree must re-declare the
|
|
116
|
+
// semantics against its own scales (otherwise the root's resolved colors win).
|
|
117
|
+
const dark = block("[data-theme='dark']", {
|
|
118
|
+
...scaleVars('dark'),
|
|
119
|
+
...semanticVars(),
|
|
120
|
+
...overrideVars('dark'),
|
|
121
|
+
});
|
|
122
|
+
const highContrast = block("[data-theme='high-contrast']", {
|
|
123
|
+
...scaleVars('high-contrast'),
|
|
124
|
+
...semanticVars(),
|
|
125
|
+
...overrideVars('high-contrast'),
|
|
126
|
+
});
|
|
127
|
+
const autoDark = `@media (prefers-color-scheme: dark) {\n${block(':root:not([data-theme])', { ...scaleVars('dark'), ...semanticVars(), ...overrideVars('dark') })
|
|
128
|
+
.split('\n')
|
|
129
|
+
.map((line) => ` ${line}`)
|
|
130
|
+
.join('\n')}\n}`;
|
|
131
|
+
const accents = Object.entries(accentPresets).map(([name, preset]) => accentBlocks(name, preset));
|
|
132
|
+
const body = [root, dark, highContrast, autoDark, ...accents].join('\n\n');
|
|
133
|
+
return `${LAYER_ORDER}\n\n@layer flx.tokens {\n${body}\n}`;
|
|
134
|
+
}
|
|
135
|
+
export const tokensCss = generateTokensCss();
|