@fluixi-ui/tokens 0.4.0-alpha.7 → 0.4.0-alpha.9

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.
@@ -0,0 +1,42 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Icon button — a square [data-variant] button. Colours / states / ripple come
4
+ * from the button slice + the active skin; this only makes it square and shapes
5
+ * the corners.
6
+ * ------------------------------------------------------------------------- */
7
+ :where([data-icon-button]) {
8
+ width: var(--flx-ui-size-md);
9
+ padding: 0;
10
+ min-width: 0;
11
+ flex: none;
12
+ }
13
+ :where([data-icon-button][data-size='sm']) {
14
+ width: var(--flx-ui-size-sm);
15
+ }
16
+ :where([data-icon-button][data-size='lg']) {
17
+ width: var(--flx-ui-size-lg);
18
+ }
19
+
20
+ /* Shape */
21
+ :where([data-icon-button][data-shape='circle']) {
22
+ border-radius: var(--flx-ui-radius-full);
23
+ }
24
+ :where([data-icon-button][data-shape='square']) {
25
+ border-radius: var(--flx-ui-radius-sm);
26
+ }
27
+
28
+ /* A sensible default icon size that tracks the button size (consumers can still
29
+ size their own glyphs). */
30
+ :where([data-icon-button] svg) {
31
+ width: 20px;
32
+ height: 20px;
33
+ }
34
+ :where([data-icon-button][data-size='sm'] svg) {
35
+ width: 16px;
36
+ height: 16px;
37
+ }
38
+ :where([data-icon-button][data-size='lg'] svg) {
39
+ width: 24px;
40
+ height: 24px;
41
+ }
42
+ }
@@ -0,0 +1,67 @@
1
+ @layer flx.components {
2
+ /* Image (from @fluixi-ui/image). A box that reserves the image's space and layers
3
+ a loading placeholder / blur-up and an error fallback over the <img>. Size it via
4
+ a width/height, an aspect-ratio, or a sized parent — the <img> fills it. */
5
+ :where([data-image]) {
6
+ position: relative;
7
+ display: inline-block;
8
+ overflow: hidden;
9
+ line-height: 0;
10
+ vertical-align: middle;
11
+ background: var(--flx-ui-color-bg-subtle);
12
+ color: var(--flx-ui-color-fg-subtle);
13
+ --flx-ui-image-fallback-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M21 3H3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2Zm0 16H3V5h18v14ZM8.5 13.5l2 2.41L13.5 12l4 5.5H6.5l2-4Z'/%3E%3C/svg%3E");
14
+ }
15
+
16
+ /* The image stays in normal flow so the box reserves its footprint (and keeps its
17
+ height when the source errors); object-fit crops it to a sized box. */
18
+ :where([data-image-img]) {
19
+ display: block;
20
+ width: 100%;
21
+ height: 100%;
22
+ object-fit: cover;
23
+ opacity: 0;
24
+ transition: opacity var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
25
+ }
26
+ :where([data-image][data-status='loaded'] [data-image-img]) {
27
+ opacity: 1;
28
+ }
29
+ :where([data-image][data-status='error'] [data-image-img]) {
30
+ opacity: 0;
31
+ }
32
+
33
+ /* Overlays — blur-up, custom placeholder, and the error fallback — fill the box. */
34
+ :where([data-image-blur], [data-image-placeholder], [data-image-fallback]) {
35
+ position: absolute;
36
+ inset: 0;
37
+ width: 100%;
38
+ height: 100%;
39
+ }
40
+ :where([data-image-blur]) {
41
+ background-size: cover;
42
+ background-position: center;
43
+ filter: blur(12px);
44
+ transform: scale(1.1);
45
+ }
46
+
47
+ /* Default error fallback: a centered picture glyph on the subtle surface. Passing a
48
+ custom `fallback` element replaces it entirely. */
49
+ :where([data-image-fallback]) {
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+ background: var(--flx-ui-color-bg-subtle);
54
+ color: var(--flx-ui-color-fg-subtle);
55
+ }
56
+ :where([data-image-fallback])::before {
57
+ content: "";
58
+ width: 45%;
59
+ height: 45%;
60
+ max-width: 28px;
61
+ max-height: 28px;
62
+ background: currentColor;
63
+ opacity: 0.55;
64
+ -webkit-mask: center / contain no-repeat var(--flx-ui-image-fallback-glyph);
65
+ mask: center / contain no-repeat var(--flx-ui-image-fallback-glyph);
66
+ }
67
+ }
@@ -0,0 +1,10 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Layout primitives (Stack / Grid / Container) — the geometry is set inline per
4
+ * instance; this only guarantees a predictable box model.
5
+ * ------------------------------------------------------------------------- */
6
+ :where([data-stack], [data-grid], [data-container]) {
7
+ box-sizing: border-box;
8
+ min-width: 0;
9
+ }
10
+ }
@@ -89,10 +89,15 @@
89
89
  border-color: var(--flx-ui-color-primary);
90
90
  }
91
91
  :where([data-mode='multiple'] [role='option'][data-selected] > [data-select-item-indicator])::after {
92
- content: '✓';
93
- color: var(--flx-ui-color-primary-fg);
94
- font-size: 0.7rem;
95
- line-height: 1;
92
+ content: '';
93
+ box-sizing: content-box;
94
+ flex: none;
95
+ width: 4px;
96
+ height: 8px;
97
+ margin-top: -2px;
98
+ border: solid var(--flx-ui-color-primary-fg);
99
+ border-width: 0 2px 2px 0;
100
+ transform: rotate(45deg);
96
101
  }
97
102
  :where([data-mode='multiple'] [role='option'][data-selected])::after {
98
103
  content: none;
@@ -0,0 +1,40 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * PIN / OTP input — a row of single-character cells
4
+ * ------------------------------------------------------------------------- */
5
+ :where([data-pin-input]) {
6
+ display: inline-flex;
7
+ align-items: center;
8
+ gap: var(--flx-ui-pin-gap, var(--flx-ui-space-2));
9
+ }
10
+ :where([data-pin-input-field]) {
11
+ box-sizing: border-box;
12
+ width: var(--flx-ui-pin-size, 2.75rem);
13
+ height: var(--flx-ui-pin-size, 2.75rem);
14
+ padding: 0;
15
+ text-align: center;
16
+ font: inherit;
17
+ font-size: var(--flx-ui-font-size-lg);
18
+ font-weight: var(--flx-ui-font-weight-medium);
19
+ font-variant-numeric: tabular-nums;
20
+ color: var(--flx-ui-color-fg);
21
+ background: var(--flx-ui-color-bg);
22
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border-strong);
23
+ border-radius: var(--flx-ui-radius-md);
24
+ transition:
25
+ border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
26
+ box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
27
+ }
28
+ :where([data-pin-input-field]::placeholder) {
29
+ color: var(--flx-ui-color-fg-muted);
30
+ }
31
+ :where([data-pin-input-field]:focus-visible) {
32
+ outline: none;
33
+ border-color: var(--flx-ui-color-focus-ring);
34
+ box-shadow: var(--flx-ui-ctl-ring);
35
+ }
36
+ :where([data-pin-input-field]:disabled) {
37
+ opacity: var(--flx-ui-opacity-disabled, 0.5);
38
+ cursor: not-allowed;
39
+ }
40
+ }
@@ -37,4 +37,60 @@
37
37
  0% { transform: translateX(-100%); }
38
38
  100% { transform: translateX(250%); }
39
39
  }
40
+
41
+ /* ----------------------------------------------------------------------------
42
+ * Circular progress
43
+ * ------------------------------------------------------------------------- */
44
+ :where([data-circular]) {
45
+ position: relative;
46
+ /* Stack the ring and any centred label in a single grid cell. */
47
+ display: inline-grid;
48
+ place-items: center;
49
+ vertical-align: middle;
50
+ /* Undo the linear bar's box styling that keys off [role='progressbar']
51
+ (fixed 6px height / 240px width / clipped overflow / filled track). */
52
+ width: auto;
53
+ height: auto;
54
+ overflow: visible;
55
+ background: none;
56
+ border-radius: 0;
57
+ }
58
+ :where([data-circular]) > * {
59
+ grid-area: 1 / 1;
60
+ }
61
+ :where([data-circular] [data-progress-svg]) {
62
+ display: block;
63
+ /* Start the arc at 12 o'clock rather than 3 o'clock. */
64
+ transform: rotate(-90deg);
65
+ }
66
+ :where([data-progress-track]) {
67
+ fill: none;
68
+ stroke: var(--flx-ui-color-bg-muted);
69
+ }
70
+ :where([data-progress-indicator]) {
71
+ fill: none;
72
+ stroke: var(--flx-ui-color-primary);
73
+ stroke-linecap: round;
74
+ transition: stroke-dashoffset var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
75
+ }
76
+ /* Indeterminate: a short arc that sweeps around the ring. */
77
+ :where([data-circular][data-state='indeterminate'] [data-progress-svg]) {
78
+ animation: flx-ui-circular-spin 0.9s linear infinite;
79
+ }
80
+ :where([data-circular][data-state='indeterminate'] [data-progress-indicator]) {
81
+ stroke-dasharray: 25 200;
82
+ transition: none;
83
+ }
84
+ @media (prefers-reduced-motion: reduce) {
85
+ :where([data-circular][data-state='indeterminate'] [data-progress-svg]) {
86
+ animation: none;
87
+ }
88
+ :where([data-circular][data-state='indeterminate'] [data-progress-indicator]) {
89
+ stroke-dasharray: none;
90
+ opacity: 0.5;
91
+ }
92
+ }
93
+ @keyframes flx-ui-circular-spin {
94
+ to { transform: rotate(270deg); }
95
+ }
40
96
  }
@@ -0,0 +1,59 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Rating
4
+ *
5
+ * Each item is a star silhouette (mask) painted in the "empty" colour; a
6
+ * ::before overlay paints the "filled" colour, revealed left-to-right via
7
+ * clip-path by the item's data-state (empty / half / full).
8
+ * ------------------------------------------------------------------------- */
9
+ :where([data-rating]) {
10
+ --flx-ui-star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.2l2.9 6.5 7 .6-5.3 4.6 1.6 6.9L12 17.7 5.8 21.4l1.6-6.9L2 9.9l7-.6z'/%3E%3C/svg%3E");
11
+ display: inline-flex;
12
+ align-items: center;
13
+ gap: var(--flx-ui-rating-gap, 2px);
14
+ outline: none;
15
+ }
16
+ :where([data-rating]:not([data-disabled]):not([data-readonly])) {
17
+ cursor: pointer;
18
+ }
19
+ :where([data-rating]:focus-visible) {
20
+ border-radius: var(--flx-ui-radius-sm);
21
+ box-shadow: var(--flx-ui-ctl-ring);
22
+ }
23
+ :where([data-rating][data-disabled]) {
24
+ opacity: var(--flx-ui-opacity-disabled, 0.5);
25
+ cursor: not-allowed;
26
+ }
27
+
28
+ :where([data-rating-item]) {
29
+ position: relative;
30
+ display: inline-block;
31
+ width: var(--flx-ui-rating-size, 20px);
32
+ height: var(--flx-ui-rating-size, 20px);
33
+ flex: none;
34
+ background: var(--flx-ui-color-border-strong);
35
+ -webkit-mask: var(--flx-ui-star) center / contain no-repeat;
36
+ mask: var(--flx-ui-star) center / contain no-repeat;
37
+ }
38
+ :where([data-rating-item])::before {
39
+ content: '';
40
+ position: absolute;
41
+ inset: 0;
42
+ background: var(--flx-ui-rating-color, var(--flx-ui-color-primary));
43
+ -webkit-mask: var(--flx-ui-star) center / contain no-repeat;
44
+ mask: var(--flx-ui-star) center / contain no-repeat;
45
+ clip-path: inset(0 100% 0 0);
46
+ transition: clip-path var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
47
+ }
48
+ :where([data-rating-item][data-state='full'])::before {
49
+ clip-path: inset(0 0 0 0);
50
+ }
51
+ :where([data-rating-item][data-state='half'])::before {
52
+ clip-path: inset(0 50% 0 0);
53
+ }
54
+ @media (prefers-reduced-motion: reduce) {
55
+ :where([data-rating-item])::before {
56
+ transition: none;
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,44 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Scroll area — a scroll container with slim, theme-aware scrollbars styled over
4
+ * the native scrollbar (Firefox `scrollbar-*` + WebKit `::-webkit-scrollbar`).
5
+ * ------------------------------------------------------------------------- */
6
+ :where([data-scroll-area]) {
7
+ position: relative;
8
+ scrollbar-width: thin;
9
+ scrollbar-color: var(--flx-ui-color-border-strong) transparent;
10
+ overscroll-behavior: contain;
11
+ }
12
+ :where([data-scroll-area][data-orientation='vertical']) {
13
+ overflow-y: auto;
14
+ overflow-x: hidden;
15
+ }
16
+ :where([data-scroll-area][data-orientation='horizontal']) {
17
+ overflow-x: auto;
18
+ overflow-y: hidden;
19
+ }
20
+ :where([data-scroll-area][data-orientation='both']) {
21
+ overflow: auto;
22
+ }
23
+ :where([data-scroll-area])::-webkit-scrollbar {
24
+ width: 10px;
25
+ height: 10px;
26
+ }
27
+ :where([data-scroll-area])::-webkit-scrollbar-track {
28
+ background: transparent;
29
+ }
30
+ :where([data-scroll-area])::-webkit-scrollbar-thumb {
31
+ background: var(--flx-ui-color-border-strong);
32
+ border-radius: var(--flx-ui-radius-full);
33
+ /* Inset the thumb with a transparent border so it reads as a slim pill. */
34
+ border: 2px solid transparent;
35
+ background-clip: content-box;
36
+ }
37
+ :where([data-scroll-area])::-webkit-scrollbar-thumb:hover {
38
+ background: var(--flx-ui-color-fg-muted);
39
+ background-clip: content-box;
40
+ }
41
+ :where([data-scroll-area])::-webkit-scrollbar-corner {
42
+ background: transparent;
43
+ }
44
+ }
@@ -42,7 +42,7 @@
42
42
  left: 0;
43
43
  width: 100%;
44
44
  }
45
- :where([role='slider']) {
45
+ :where([data-slider] [role='slider']) {
46
46
  position: absolute;
47
47
  width: 16px;
48
48
  height: 16px;
@@ -52,15 +52,15 @@
52
52
  box-shadow: var(--flx-ui-shadow-sm);
53
53
  cursor: grab;
54
54
  }
55
- :where([role='slider'][data-orientation='horizontal']) {
55
+ :where([data-slider] [role='slider'][data-orientation='horizontal']) {
56
56
  top: 50%;
57
57
  transform: translate(-50%, -50%);
58
58
  }
59
- :where([role='slider'][data-orientation='vertical']) {
59
+ :where([data-slider] [role='slider'][data-orientation='vertical']) {
60
60
  left: 50%;
61
61
  transform: translate(-50%, 50%);
62
62
  }
63
- :where([role='slider']:focus-visible) {
63
+ :where([data-slider] [role='slider']:focus-visible) {
64
64
  outline: none;
65
65
  box-shadow: var(--flx-ui-ctl-ring);
66
66
  }
@@ -0,0 +1,85 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Speed dial — a FAB trigger with actions that fan out in a direction.
4
+ * ------------------------------------------------------------------------- */
5
+ :where([data-speed-dial]) {
6
+ position: relative;
7
+ display: inline-flex;
8
+ width: max-content;
9
+ }
10
+ :where([data-speed-dial-actions]) {
11
+ position: absolute;
12
+ display: flex;
13
+ gap: var(--flx-ui-space-3);
14
+ align-items: center;
15
+ opacity: 1;
16
+ transition: opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
17
+ }
18
+ /* Off by default — the actions float bare. `filled` opts into a surface. */
19
+ :where([data-speed-dial-actions][data-filled]) {
20
+ gap: var(--flx-ui-space-2);
21
+ padding: var(--flx-ui-space-2);
22
+ background: var(--flx-ui-color-bg);
23
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
24
+ border-radius: var(--flx-ui-radius-lg);
25
+ box-shadow: var(--flx-ui-shadow-lg);
26
+ }
27
+ /* Hidden (and unfocusable) until the dial opens. */
28
+ :where([data-speed-dial][data-state='closed']) [data-speed-dial-actions] {
29
+ visibility: hidden;
30
+ opacity: 0;
31
+ pointer-events: none;
32
+ }
33
+
34
+ /* Fan-out direction, anchored to the trigger. */
35
+ :where([data-speed-dial][data-direction='up']) [data-speed-dial-actions] {
36
+ flex-direction: column-reverse;
37
+ bottom: 100%;
38
+ left: 50%;
39
+ transform: translateX(-50%);
40
+ margin-bottom: var(--flx-ui-space-3);
41
+ }
42
+ :where([data-speed-dial][data-direction='down']) [data-speed-dial-actions] {
43
+ flex-direction: column;
44
+ top: 100%;
45
+ left: 50%;
46
+ transform: translateX(-50%);
47
+ margin-top: var(--flx-ui-space-3);
48
+ }
49
+ :where([data-speed-dial][data-direction='left']) [data-speed-dial-actions] {
50
+ flex-direction: row-reverse;
51
+ right: 100%;
52
+ top: 50%;
53
+ transform: translateY(-50%);
54
+ margin-right: var(--flx-ui-space-3);
55
+ }
56
+ :where([data-speed-dial][data-direction='right']) [data-speed-dial-actions] {
57
+ flex-direction: row;
58
+ left: 100%;
59
+ top: 50%;
60
+ transform: translateY(-50%);
61
+ margin-left: var(--flx-ui-space-3);
62
+ }
63
+
64
+ /* An action + its optional label. */
65
+ :where([data-speed-dial-action-wrap]) {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ gap: var(--flx-ui-space-2);
69
+ }
70
+ /* For vertical directions the label reads to the left of the button. */
71
+ :where([data-speed-dial][data-direction='down']) [data-speed-dial-action-wrap],
72
+ :where([data-speed-dial][data-direction='up']) [data-speed-dial-action-wrap] {
73
+ flex-direction: row-reverse;
74
+ }
75
+ :where([data-speed-dial-action-label]) {
76
+ padding: 2px var(--flx-ui-space-2);
77
+ border-radius: var(--flx-ui-radius-sm);
78
+ background: var(--flx-ui-color-fg);
79
+ color: var(--flx-ui-color-bg);
80
+ font-size: var(--flx-ui-font-size-xs);
81
+ font-weight: var(--flx-ui-font-weight-medium);
82
+ white-space: nowrap;
83
+ box-shadow: var(--flx-ui-shadow-sm);
84
+ }
85
+ }
@@ -0,0 +1,34 @@
1
+ @layer flx.components {
2
+ /* Statistic (from @fluixi-ui/statistic). A KPI: caption, big value, and an
3
+ optional delta line coloured by the container's data-trend. */
4
+ :where([data-statistic]) {
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: var(--flx-ui-space-1);
8
+ min-width: 0;
9
+ }
10
+ :where([data-statistic-label]) {
11
+ font-size: var(--flx-ui-font-size-xs);
12
+ font-weight: var(--flx-ui-font-weight-medium);
13
+ color: var(--flx-ui-color-fg-subtle);
14
+ letter-spacing: var(--flx-ui-letter-spacing-wide);
15
+ text-transform: uppercase;
16
+ }
17
+ :where([data-statistic-value]) {
18
+ font-size: var(--flx-ui-font-size-2xl);
19
+ font-weight: var(--flx-ui-font-weight-bold);
20
+ color: var(--flx-ui-color-fg);
21
+ line-height: 1.1;
22
+ font-variant-numeric: tabular-nums;
23
+ }
24
+ :where([data-statistic-delta]) {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ gap: var(--flx-ui-space-1);
28
+ font-size: var(--flx-ui-font-size-xs);
29
+ font-weight: var(--flx-ui-font-weight-semibold);
30
+ color: var(--flx-ui-color-fg-subtle);
31
+ }
32
+ :where([data-statistic][data-trend='up'] [data-statistic-delta]) { color: var(--flx-ui-color-success); }
33
+ :where([data-statistic][data-trend='down'] [data-statistic-delta]) { color: var(--flx-ui-color-danger); }
34
+ }
@@ -170,4 +170,15 @@
170
170
  :where([data-pinned='right']) {
171
171
  box-shadow: inset 1px 0 0 var(--flx-ui-color-border);
172
172
  }
173
+
174
+ /* Compact density — tighter padding + smaller type for dense data tables.
175
+ * Opt in with `data-density="compact"` on the table. */
176
+ [data-table][data-density='compact'] [data-table-head],
177
+ [data-table][data-density='compact'] [data-table-cell],
178
+ [data-table][data-density='compact'] [data-table-footer] [data-table-cell] {
179
+ padding: var(--flx-ui-space-1) var(--flx-ui-space-2);
180
+ }
181
+ [data-table][data-density='compact'] [data-table-cell] {
182
+ font-size: var(--flx-ui-font-size-xs);
183
+ }
173
184
  }
@@ -0,0 +1,91 @@
1
+ @layer flx.components {
2
+ /* Tags input (from @fluixi-ui/tags-input) — a chip field. The container owns the
3
+ frame + focus ring; the inner input is borderless and grows to fill the row. */
4
+ :where([data-tags-input]) {
5
+ box-sizing: border-box;
6
+ display: flex;
7
+ flex-wrap: wrap;
8
+ align-items: center;
9
+ gap: var(--flx-ui-space-1);
10
+ width: 100%;
11
+ min-height: var(--flx-ui-size-md);
12
+ padding: var(--flx-ui-space-1) var(--flx-ui-space-2);
13
+ color: var(--flx-ui-color-fg);
14
+ background: var(--flx-ui-color-bg);
15
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border-strong);
16
+ border-radius: var(--flx-ui-radius-md);
17
+ cursor: text;
18
+ transition:
19
+ border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
20
+ box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
21
+ }
22
+ :where([data-tags-input]:focus-within) {
23
+ border-color: var(--flx-ui-color-focus-ring);
24
+ box-shadow: var(--flx-ui-ctl-ring);
25
+ }
26
+ :where([data-tags-input][data-invalid]) {
27
+ border-color: var(--flx-ui-color-danger);
28
+ }
29
+ :where([data-tags-input][data-disabled]) {
30
+ opacity: 0.6;
31
+ cursor: not-allowed;
32
+ }
33
+
34
+ /* The editable input; borderless, inherits the field's type + colour. */
35
+ :where([data-tags-input-field]) {
36
+ flex: 1 1 6ch;
37
+ min-width: 6ch;
38
+ min-height: 0;
39
+ border: none;
40
+ outline: none;
41
+ box-shadow: none;
42
+ background: none;
43
+ font: inherit;
44
+ color: inherit;
45
+ padding: var(--flx-ui-space-1);
46
+ }
47
+ :where([data-tags-input-field]::placeholder) {
48
+ color: var(--flx-ui-color-fg-muted);
49
+ }
50
+
51
+ /* A committed tag chip. */
52
+ :where([data-tags-input-tag]) {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: var(--flx-ui-space-1);
56
+ padding: 0 var(--flx-ui-space-1) 0 var(--flx-ui-space-2);
57
+ height: calc(var(--flx-ui-size-md) - 2 * var(--flx-ui-space-2));
58
+ font-size: var(--flx-ui-font-size-sm);
59
+ color: var(--flx-ui-color-fg);
60
+ background: var(--flx-ui-color-bg-subtle);
61
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
62
+ border-radius: var(--flx-ui-radius-sm);
63
+ white-space: nowrap;
64
+ }
65
+ :where([data-tags-input-tag-remove]) {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 1.3em;
70
+ height: 1.3em;
71
+ padding: 0;
72
+ font: inherit;
73
+ line-height: 1;
74
+ color: var(--flx-ui-color-fg-muted);
75
+ background: none;
76
+ border: none;
77
+ border-radius: var(--flx-ui-radius-sm);
78
+ cursor: pointer;
79
+ transition:
80
+ color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
81
+ background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
82
+ }
83
+ :where([data-tags-input-tag-remove]:hover) {
84
+ color: var(--flx-ui-color-fg);
85
+ background: color-mix(in oklab, var(--flx-ui-color-fg) 10%, transparent);
86
+ }
87
+ :where([data-tags-input-tag-remove]:focus-visible) {
88
+ outline: none;
89
+ box-shadow: var(--flx-ui-ctl-ring);
90
+ }
91
+ }
@@ -0,0 +1,59 @@
1
+ @layer flx.components {
2
+ /* Timeline (from @fluixi-ui/timeline). A vertical feed: each item is an indicator
3
+ column (dot) beside its content; a connector line threads between the dots. */
4
+ :where([data-timeline]) {
5
+ display: flex;
6
+ flex-direction: column;
7
+ }
8
+ :where([data-timeline-item]) {
9
+ display: grid;
10
+ grid-template-columns: auto 1fr;
11
+ column-gap: var(--flx-ui-space-3);
12
+ --flx-timeline-dot: var(--flx-ui-color-border-strong);
13
+ }
14
+ :where([data-timeline-item][data-tone='primary']),
15
+ :where([data-timeline-item][data-tone='info']) { --flx-timeline-dot: var(--flx-ui-color-primary); }
16
+ :where([data-timeline-item][data-tone='success']) { --flx-timeline-dot: var(--flx-ui-color-success); }
17
+ :where([data-timeline-item][data-tone='warning']) { --flx-timeline-dot: var(--flx-ui-color-warning); }
18
+ :where([data-timeline-item][data-tone='danger']) { --flx-timeline-dot: var(--flx-ui-color-danger); }
19
+
20
+ /* Indicator column: the dot, with the connector line drawn below it. */
21
+ :where([data-timeline-indicator]) {
22
+ position: relative;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ width: 18px;
27
+ padding-top: 2px;
28
+ color: var(--flx-timeline-dot);
29
+ }
30
+ /* Default dot (when no custom `dot` content). */
31
+ :where([data-timeline-indicator]:empty)::before {
32
+ content: "";
33
+ width: 9px;
34
+ height: 9px;
35
+ border-radius: var(--flx-ui-radius-full);
36
+ background: var(--flx-timeline-dot);
37
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--flx-timeline-dot) 18%, transparent);
38
+ }
39
+ /* Connector line — hidden on the last item. */
40
+ :where([data-timeline-item]:not(:last-child) [data-timeline-indicator])::after {
41
+ content: "";
42
+ position: absolute;
43
+ top: 16px;
44
+ bottom: -6px;
45
+ left: 50%;
46
+ width: var(--flx-ui-border-width-thin);
47
+ transform: translateX(-50%);
48
+ background: var(--flx-ui-color-border);
49
+ }
50
+ :where([data-timeline-content]) {
51
+ min-width: 0;
52
+ padding-bottom: var(--flx-ui-space-4);
53
+ font-size: var(--flx-ui-font-size-sm);
54
+ color: var(--flx-ui-color-fg);
55
+ }
56
+ :where([data-timeline-item]:last-child [data-timeline-content]) {
57
+ padding-bottom: 0;
58
+ }
59
+ }