@kahitsan/ksui 0.33.0 → 0.34.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 +4 -1
- package/src/components/base/AccountAvatar.tsx +1 -1
- package/src/components/base/AddAttachmentTile.tsx +4 -4
- package/src/components/base/BadgeSelect.tsx +10 -10
- package/src/components/base/Button.tsx +70 -38
- package/src/components/base/CameraCapture.tsx +8 -8
- package/src/components/base/ChartLegend.tsx +3 -3
- package/src/components/base/CopyButton.tsx +1 -1
- package/src/components/base/DataTable.tsx +68 -72
- package/src/components/base/DatePicker.tsx +77 -82
- package/src/components/base/DateTile.tsx +7 -7
- package/src/components/base/DetailRow.tsx +2 -2
- package/src/components/base/Dropdown.tsx +15 -15
- package/src/components/base/ExistingAttachmentTile.tsx +6 -6
- package/src/components/base/EyebrowBadge.tsx +15 -15
- package/src/components/base/FormErrorBanner.tsx +1 -1
- package/src/components/base/FormField.tsx +2 -2
- package/src/components/base/ImageCropper.tsx +19 -19
- package/src/components/base/ImageViewer.tsx +7 -11
- package/src/components/base/KpiCard.tsx +7 -7
- package/src/components/base/Modal.tsx +10 -14
- package/src/components/base/ProgressBar.tsx +63 -59
- package/src/components/base/RadioCardGroup.tsx +13 -6
- package/src/components/base/SectionHeading.tsx +4 -4
- package/src/components/base/SegmentedFilter.tsx +60 -4
- package/src/components/base/SocialLinksBar.tsx +3 -15
- package/src/components/base/StatusIndicator.tsx +11 -11
- package/src/components/base/StatusPill.tsx +15 -15
- package/src/components/base/TagPill.tsx +1 -1
- package/src/components/base/ThemeToggle.tsx +12 -20
- package/src/components/base/Tooltip.tsx +2 -2
- package/src/components/composite/AccountRadioPicker.tsx +8 -4
- package/src/components/composite/ComboBox.tsx +51 -44
- package/src/components/composite/CustomRenderer.tsx +6 -13
- package/src/components/composite/FileField.tsx +10 -17
- package/src/components/composite/FlowGraph.tsx +29 -36
- package/src/components/composite/FlowRunner.tsx +11 -18
- package/src/components/composite/FormAdvancedSection.tsx +31 -31
- package/src/components/composite/LiveTimer.tsx +3 -3
- package/src/components/composite/MarkdownNotes.tsx +11 -11
- package/src/components/composite/MentionTextarea.tsx +10 -10
- package/src/components/composite/NotFound.tsx +7 -7
- package/src/components/composite/PaymentAccountPicker.tsx +19 -19
- package/src/components/composite/SalesBodyEditor.tsx +26 -26
- package/src/components/composite/SearchableSelect.tsx +14 -14
- package/src/components/composite/SecretReveal.tsx +5 -5
- package/src/components/composite/TransactionAccountFields.tsx +78 -0
- package/src/components/composite/TransactionForm.tsx +83 -169
- package/src/components/composite/TransactionPayableFields.tsx +97 -0
- package/src/components/composite/TransferAccountsPicker.tsx +20 -20
- package/src/components/composite/TransferFeeChip.tsx +2 -2
- package/src/components/composite/VoucherPicker.tsx +25 -25
- package/src/components/composite/resource/ResourceForm.tsx +1 -1
- package/src/components/composite/resource/ResourcePage.tsx +8 -8
- package/src/components/composite/resource/cells.tsx +6 -3
- package/src/utils/INPUT_CLASS.ts +6 -3
- package/src/utils/account-icons.ts +29 -7
- package/src/utils/highlight.tsx +5 -6
- package/src/utils/inject-css.ts +13 -0
- package/tailwind.js +11 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahitsan/ksui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "ksui is a standalone set of SolidJS UI components for KahitSan/Hilinga and any SolidJS app. Published to the public npm registry and consumed as a normal dependency. Ships source under a `solid` export condition so the consumer's vite-plugin-solid compiles it with only solid-js externalized; it depends on nothing but solid-js + lucide-solid and injects its own CSS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
34
|
"typecheck": "tsc --noEmit",
|
|
35
|
+
"check:tokens": "node scripts/check-token-fallbacks.mjs",
|
|
36
|
+
"check:coverage": "node scripts/check-token-fallbacks.mjs --coverage",
|
|
37
|
+
"check:palette": "node scripts/check-palette-drift.mjs",
|
|
35
38
|
"test": "vitest run",
|
|
36
39
|
"test:watch": "vitest",
|
|
37
40
|
"test:e2e": "playwright test",
|
|
@@ -117,7 +117,7 @@ export default function AccountAvatar(props: AccountAvatarProps) {
|
|
|
117
117
|
<Dynamic
|
|
118
118
|
component={getAccountIcon(props.account)}
|
|
119
119
|
size={iconSize()}
|
|
120
|
-
class={props.iconClass ?? "text-
|
|
120
|
+
class={props.iconClass ?? "text-[var(--ks-fg-muted,#a1a1aa)]"}
|
|
121
121
|
style={iconStyle()}
|
|
122
122
|
/>
|
|
123
123
|
);
|
|
@@ -53,7 +53,7 @@ export default function AddAttachmentTile(props: Props) {
|
|
|
53
53
|
setOpen(!open());
|
|
54
54
|
}}
|
|
55
55
|
disabled={props.uploading}
|
|
56
|
-
class="w-24 h-24 flex flex-col items-center justify-center gap-1 border border-dashed border-
|
|
56
|
+
class="w-24 h-24 flex flex-col items-center justify-center gap-1 border border-dashed border-[var(--ks-border-strong,#3f3f46)] bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_30%,transparent)] text-[var(--ks-fg-muted,#a1a1aa)] hover:bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_60%,transparent)] hover:border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_40%,transparent)] hover:text-[var(--ks-accent,#fbbf24)] active:bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_80%,transparent)] transition-colors ks-hud-clip-top-left-bottom-right cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
|
57
57
|
aria-label="Add attachment"
|
|
58
58
|
>
|
|
59
59
|
<Plus size={22} />
|
|
@@ -64,11 +64,11 @@ export default function AddAttachmentTile(props: Props) {
|
|
|
64
64
|
<div
|
|
65
65
|
ref={menu}
|
|
66
66
|
style={{ top: `${pos().top}px`, left: `${pos().left}px` }}
|
|
67
|
-
class="fixed z-[60] min-w-[160px] rounded-lg border border-
|
|
67
|
+
class="fixed z-[60] min-w-[160px] rounded-lg border border-[var(--ks-border-strong,#3f3f46)] bg-[var(--ks-surface-raised,#1a1a1a)] shadow-2xl p-1 ks-hud-clip-top-left-bottom-right"
|
|
68
68
|
>
|
|
69
69
|
<button
|
|
70
70
|
type="button"
|
|
71
|
-
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-
|
|
71
|
+
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-[var(--ks-fg,#ffffff)] hover:bg-[var(--ks-surface-sunken,#141414)] rounded cursor-pointer"
|
|
72
72
|
onClick={() => {
|
|
73
73
|
setOpen(false);
|
|
74
74
|
props.onPickCamera();
|
|
@@ -79,7 +79,7 @@ export default function AddAttachmentTile(props: Props) {
|
|
|
79
79
|
</button>
|
|
80
80
|
<button
|
|
81
81
|
type="button"
|
|
82
|
-
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-
|
|
82
|
+
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-[var(--ks-fg,#ffffff)] hover:bg-[var(--ks-surface-sunken,#141414)] rounded cursor-pointer"
|
|
83
83
|
onClick={() => {
|
|
84
84
|
setOpen(false);
|
|
85
85
|
props.onPickFile();
|
|
@@ -76,7 +76,7 @@ export default function BadgeSelect(props: BadgeSelectProps): JSX.Element {
|
|
|
76
76
|
);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
const defaultBadge = () => "bg-
|
|
79
|
+
const defaultBadge = () => "bg-[var(--ks-surface-raised,#1a1a1a)] text-[var(--ks-fg-muted,#a1a1aa)] border border-transparent";
|
|
80
80
|
const badgeClass = () => props.badgeClass?.(props.value) ?? defaultBadge();
|
|
81
81
|
|
|
82
82
|
const emptyStateMessage = () => {
|
|
@@ -164,7 +164,7 @@ export default function BadgeSelect(props: BadgeSelectProps): JSX.Element {
|
|
|
164
164
|
aria-expanded={open()}
|
|
165
165
|
disabled={props.disabled || busy()}
|
|
166
166
|
onClick={() => !props.disabled && setOpen((o) => !o)}
|
|
167
|
-
class={`text-xs font-medium px-1.5 py-0.5 rounded cursor-pointer transition-colors hover:ring-1 hover:ring-
|
|
167
|
+
class={`text-xs font-medium px-1.5 py-0.5 rounded cursor-pointer transition-colors hover:ring-1 hover:ring-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_40%,transparent)] ${badgeClass()} ${
|
|
168
168
|
props.disabled ? "cursor-not-allowed opacity-60" : ""
|
|
169
169
|
}`}
|
|
170
170
|
title={props.disabled ? "" : (props.title ?? "Click to change")}
|
|
@@ -176,25 +176,25 @@ export default function BadgeSelect(props: BadgeSelectProps): JSX.Element {
|
|
|
176
176
|
<div
|
|
177
177
|
ref={popupRef}
|
|
178
178
|
role="listbox"
|
|
179
|
-
class="z-[100] rounded-md border border-
|
|
179
|
+
class="z-[100] rounded-md border border-[var(--ks-border-strong,#3f3f46)] bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_95%,transparent)] backdrop-blur shadow-xl overflow-hidden flex flex-col"
|
|
180
180
|
style={popupStyle()}
|
|
181
181
|
>
|
|
182
182
|
<Show when={showSearch()}>
|
|
183
|
-
<div class="px-2 py-1.5 border-b border-
|
|
183
|
+
<div class="px-2 py-1.5 border-b border-[var(--ks-border,rgba(39,39,42,0.5))]">
|
|
184
184
|
<input
|
|
185
185
|
ref={searchRef}
|
|
186
186
|
type="text"
|
|
187
187
|
value={query()}
|
|
188
188
|
onInput={(e) => setQuery(e.currentTarget.value)}
|
|
189
189
|
placeholder={props.searchPlaceholder ?? "Search…"}
|
|
190
|
-
class="w-full px-2 py-1 text-xs bg-
|
|
190
|
+
class="w-full px-2 py-1 text-xs bg-[var(--ks-surface-sunken,#141414)] border border-[var(--ks-border,rgba(39,39,42,0.5))] rounded text-[var(--ks-fg,#ffffff)] placeholder:text-[var(--ks-input-placeholder,#71717a)] focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_50%,transparent)]"
|
|
191
191
|
/>
|
|
192
192
|
</div>
|
|
193
193
|
</Show>
|
|
194
194
|
<div class="flex-1 overflow-y-auto">
|
|
195
195
|
<Show
|
|
196
196
|
when={!props.loading && filtered().length > 0}
|
|
197
|
-
fallback={<div class="px-3 py-2 text-xs text-
|
|
197
|
+
fallback={<div class="px-3 py-2 text-xs text-[var(--ks-fg-subtle,#71717a)]">{emptyStateMessage()}</div>}
|
|
198
198
|
>
|
|
199
199
|
<For each={filtered()}>
|
|
200
200
|
{(opt) => (
|
|
@@ -203,18 +203,18 @@ export default function BadgeSelect(props: BadgeSelectProps): JSX.Element {
|
|
|
203
203
|
role="option"
|
|
204
204
|
aria-selected={opt.value === props.value}
|
|
205
205
|
onClick={() => handleSelect(opt.value)}
|
|
206
|
-
class={`w-full text-left px-3 py-2 text-xs hover:bg-
|
|
207
|
-
opt.value === props.value ? "text-
|
|
206
|
+
class={`w-full text-left px-3 py-2 text-xs hover:bg-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_10%,transparent)] transition-colors flex items-center justify-between gap-2 ${
|
|
207
|
+
opt.value === props.value ? "text-[var(--ks-accent,#fbbf24)]" : "text-[var(--ks-fg,#ffffff)]"
|
|
208
208
|
}`}
|
|
209
209
|
>
|
|
210
210
|
<span class="flex flex-col">
|
|
211
211
|
<span class="font-medium">{opt.label}</span>
|
|
212
212
|
<Show when={opt.description}>
|
|
213
|
-
<span class="text-[10px] text-
|
|
213
|
+
<span class="text-[10px] text-[var(--ks-fg-subtle,#71717a)]">{opt.description}</span>
|
|
214
214
|
</Show>
|
|
215
215
|
</span>
|
|
216
216
|
<Show when={opt.value === props.value}>
|
|
217
|
-
<span class="text-
|
|
217
|
+
<span class="text-[var(--ks-accent,#fbbf24)]" aria-hidden="true">✓</span>
|
|
218
218
|
</Show>
|
|
219
219
|
</button>
|
|
220
220
|
)}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type Component,
|
|
24
24
|
} from "solid-js";
|
|
25
25
|
import { Dynamic } from "solid-js/web";
|
|
26
|
+
import { injectCSS } from "../../utils/inject-css";
|
|
26
27
|
|
|
27
28
|
const BUTTON_STYLE_ID = "ks-button-inline-style";
|
|
28
29
|
const BUTTON_CSS = `
|
|
@@ -49,12 +50,7 @@ const BUTTON_CSS = `
|
|
|
49
50
|
`;
|
|
50
51
|
|
|
51
52
|
function ensureButtonStyle() {
|
|
52
|
-
|
|
53
|
-
if (document.getElementById(BUTTON_STYLE_ID)) return;
|
|
54
|
-
const el = document.createElement("style");
|
|
55
|
-
el.id = BUTTON_STYLE_ID;
|
|
56
|
-
el.textContent = BUTTON_CSS;
|
|
57
|
-
document.head.appendChild(el);
|
|
53
|
+
injectCSS(BUTTON_STYLE_ID, BUTTON_CSS);
|
|
58
54
|
}
|
|
59
55
|
|
|
60
56
|
const styles: Record<string, string> = {
|
|
@@ -106,12 +102,24 @@ function cn(...classes: Array<string | undefined | null | false>): string {
|
|
|
106
102
|
return classes.filter(Boolean).join(" ");
|
|
107
103
|
}
|
|
108
104
|
|
|
105
|
+
// Intent → design token: the button's fill/border/glow effects are all
|
|
106
|
+
// derived from a single base token per intent. Every effect string below is
|
|
107
|
+
// written out literally (not built from a template at runtime) because the
|
|
108
|
+
// theming rollout's check-token-fallbacks.mjs gate statically greps source
|
|
109
|
+
// text for the color-mix CSS function with a literal --ks-* var name — an
|
|
110
|
+
// interpolated `${token.name}` would read as malformed to that gate even
|
|
111
|
+
// though the runtime output is correct.
|
|
109
112
|
interface IntentColors {
|
|
110
113
|
ripple: string;
|
|
111
|
-
|
|
114
|
+
effectColor: string;
|
|
112
115
|
effectBg: string;
|
|
113
116
|
effectBorder: string;
|
|
117
|
+
effectBgHover: string;
|
|
118
|
+
effectBorderHover: string;
|
|
114
119
|
effectGlow: string;
|
|
120
|
+
effectGlowHover: string;
|
|
121
|
+
effectPulseBgMid: string;
|
|
122
|
+
effectPulseGlow: string;
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
interface IntentConfig {
|
|
@@ -121,41 +129,64 @@ interface IntentConfig {
|
|
|
121
129
|
colors: IntentColors;
|
|
122
130
|
}
|
|
123
131
|
|
|
132
|
+
const RIPPLE = "color-mix(in srgb, var(--ks-fg, #ffffff) 30%, transparent)";
|
|
133
|
+
|
|
124
134
|
const buttonIntentConfig: Record<ButtonIntent, IntentConfig> = {
|
|
125
135
|
primary: {
|
|
126
|
-
textColor: "text-
|
|
127
|
-
background:
|
|
128
|
-
|
|
136
|
+
textColor: "text-[var(--ks-accent,#fbbf24)]",
|
|
137
|
+
background:
|
|
138
|
+
"bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_20%,transparent)] border-[color-mix(in_srgb,var(--ks-primary,#c9a961)_60%,transparent)]",
|
|
139
|
+
hover:
|
|
140
|
+
"hover:bg-[color-mix(in_srgb,var(--ks-primary-hover,#d4b876)_30%,transparent)] hover:border-[var(--ks-primary-hover,#d4b876)]",
|
|
129
141
|
colors: {
|
|
130
|
-
ripple:
|
|
131
|
-
|
|
132
|
-
effectBg: "
|
|
133
|
-
effectBorder: "
|
|
134
|
-
|
|
142
|
+
ripple: RIPPLE,
|
|
143
|
+
effectColor: "color-mix(in srgb, var(--ks-primary, #c9a961) 40%, transparent)",
|
|
144
|
+
effectBg: "color-mix(in srgb, var(--ks-primary, #c9a961) 10%, transparent)",
|
|
145
|
+
effectBorder: "color-mix(in srgb, var(--ks-primary, #c9a961) 40%, transparent)",
|
|
146
|
+
effectBgHover: "color-mix(in srgb, var(--ks-primary, #c9a961) 20%, transparent)",
|
|
147
|
+
effectBorderHover: "color-mix(in srgb, var(--ks-primary, #c9a961) 60%, transparent)",
|
|
148
|
+
effectGlow: "color-mix(in srgb, var(--ks-primary, #c9a961) 30%, transparent)",
|
|
149
|
+
effectGlowHover: "color-mix(in srgb, var(--ks-primary, #c9a961) 50%, transparent)",
|
|
150
|
+
effectPulseBgMid: "color-mix(in srgb, var(--ks-primary, #c9a961) 20%, transparent)",
|
|
151
|
+
effectPulseGlow: "color-mix(in srgb, var(--ks-primary, #c9a961) 20%, transparent)",
|
|
135
152
|
},
|
|
136
153
|
},
|
|
137
154
|
danger: {
|
|
138
|
-
textColor: "text-
|
|
139
|
-
background:
|
|
140
|
-
|
|
155
|
+
textColor: "text-[var(--ks-danger-fg,#f87171)]",
|
|
156
|
+
background:
|
|
157
|
+
"bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_20%,transparent)] border-[color-mix(in_srgb,var(--ks-danger,#ef4444)_60%,transparent)]",
|
|
158
|
+
hover:
|
|
159
|
+
"hover:bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_30%,transparent)] hover:border-[var(--ks-danger,#ef4444)]",
|
|
141
160
|
colors: {
|
|
142
|
-
ripple:
|
|
143
|
-
|
|
144
|
-
effectBg: "
|
|
145
|
-
effectBorder: "
|
|
146
|
-
|
|
161
|
+
ripple: RIPPLE,
|
|
162
|
+
effectColor: "color-mix(in srgb, var(--ks-danger, #ef4444) 40%, transparent)",
|
|
163
|
+
effectBg: "color-mix(in srgb, var(--ks-danger, #ef4444) 10%, transparent)",
|
|
164
|
+
effectBorder: "color-mix(in srgb, var(--ks-danger, #ef4444) 40%, transparent)",
|
|
165
|
+
effectBgHover: "color-mix(in srgb, var(--ks-danger, #ef4444) 20%, transparent)",
|
|
166
|
+
effectBorderHover: "color-mix(in srgb, var(--ks-danger, #ef4444) 60%, transparent)",
|
|
167
|
+
effectGlow: "color-mix(in srgb, var(--ks-danger, #ef4444) 30%, transparent)",
|
|
168
|
+
effectGlowHover: "color-mix(in srgb, var(--ks-danger, #ef4444) 50%, transparent)",
|
|
169
|
+
effectPulseBgMid: "color-mix(in srgb, var(--ks-danger, #ef4444) 20%, transparent)",
|
|
170
|
+
effectPulseGlow: "color-mix(in srgb, var(--ks-danger, #ef4444) 20%, transparent)",
|
|
147
171
|
},
|
|
148
172
|
},
|
|
149
173
|
secondary: {
|
|
150
|
-
textColor: "text-
|
|
151
|
-
background:
|
|
152
|
-
|
|
174
|
+
textColor: "text-[var(--ks-fg-muted,#a1a1aa)]",
|
|
175
|
+
background:
|
|
176
|
+
"bg-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_20%,transparent)] border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_60%,transparent)]",
|
|
177
|
+
hover:
|
|
178
|
+
"hover:bg-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_30%,transparent)] hover:border-[var(--ks-fg-muted,#a1a1aa)]",
|
|
153
179
|
colors: {
|
|
154
|
-
ripple:
|
|
155
|
-
|
|
156
|
-
effectBg: "
|
|
157
|
-
effectBorder: "
|
|
158
|
-
|
|
180
|
+
ripple: RIPPLE,
|
|
181
|
+
effectColor: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 40%, transparent)",
|
|
182
|
+
effectBg: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 10%, transparent)",
|
|
183
|
+
effectBorder: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 40%, transparent)",
|
|
184
|
+
effectBgHover: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 20%, transparent)",
|
|
185
|
+
effectBorderHover: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 60%, transparent)",
|
|
186
|
+
effectGlow: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 30%, transparent)",
|
|
187
|
+
effectGlowHover: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 50%, transparent)",
|
|
188
|
+
effectPulseBgMid: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 20%, transparent)",
|
|
189
|
+
effectPulseGlow: "color-mix(in srgb, var(--ks-border-strong, #3f3f46) 20%, transparent)",
|
|
159
190
|
},
|
|
160
191
|
},
|
|
161
192
|
};
|
|
@@ -289,16 +320,16 @@ const Button = (props: ButtonProps): JSX.Element => {
|
|
|
289
320
|
const customProperties = createMemo(() => {
|
|
290
321
|
const colors = intentConfig().colors;
|
|
291
322
|
return {
|
|
292
|
-
"--ks-effect-color": colors.
|
|
323
|
+
"--ks-effect-color": colors.effectColor,
|
|
293
324
|
"--ks-effect-bg": colors.effectBg,
|
|
294
325
|
"--ks-effect-border": colors.effectBorder,
|
|
295
|
-
"--ks-effect-bg-hover": colors.
|
|
296
|
-
"--ks-effect-border-hover": colors.
|
|
326
|
+
"--ks-effect-bg-hover": colors.effectBgHover,
|
|
327
|
+
"--ks-effect-border-hover": colors.effectBorderHover,
|
|
297
328
|
"--ks-effect-glow": colors.effectGlow,
|
|
298
|
-
"--ks-effect-glow-hover": colors.
|
|
329
|
+
"--ks-effect-glow-hover": colors.effectGlowHover,
|
|
299
330
|
"--ks-effect-pulse-bg": colors.effectBg,
|
|
300
|
-
"--ks-effect-pulse-bg-mid": colors.
|
|
301
|
-
"--ks-effect-pulse-glow": colors.
|
|
331
|
+
"--ks-effect-pulse-bg-mid": colors.effectPulseBgMid,
|
|
332
|
+
"--ks-effect-pulse-glow": colors.effectPulseGlow,
|
|
302
333
|
"--ks-effect-pulse-glow-mid": colors.effectGlow,
|
|
303
334
|
"--ks-ripple-color": colors.ripple,
|
|
304
335
|
} as JSX.CSSProperties;
|
|
@@ -416,7 +447,8 @@ const Button = (props: ButtonProps): JSX.Element => {
|
|
|
416
447
|
top: `${ripple.y - ripple.size / 2}px`,
|
|
417
448
|
width: `${ripple.size}px`,
|
|
418
449
|
height: `${ripple.size}px`,
|
|
419
|
-
"background-color":
|
|
450
|
+
"background-color":
|
|
451
|
+
"var(--ks-ripple-color, color-mix(in srgb, var(--ks-fg, #ffffff) 30%, transparent))",
|
|
420
452
|
}}
|
|
421
453
|
/>
|
|
422
454
|
)}
|
|
@@ -75,19 +75,19 @@ export default function CameraCapture(props: Props) {
|
|
|
75
75
|
return (
|
|
76
76
|
<div
|
|
77
77
|
data-testid="camera-capture-modal"
|
|
78
|
-
class="fixed inset-0 z-[60] flex items-center justify-center bg-
|
|
78
|
+
class="fixed inset-0 z-[60] flex items-center justify-center bg-[var(--ks-overlay,rgba(0,0,0,0.7))] backdrop-blur-sm p-4"
|
|
79
79
|
>
|
|
80
|
-
<div class="card-bg rounded-xl border border-
|
|
81
|
-
<div class="flex items-center justify-between p-3 border-b border-
|
|
80
|
+
<div class="card-bg rounded-xl border border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_30%,transparent)] overflow-hidden max-w-lg w-full shadow-2xl">
|
|
81
|
+
<div class="flex items-center justify-between p-3 border-b border-[var(--ks-border,rgba(39,39,42,0.5))]">
|
|
82
82
|
<span
|
|
83
|
-
class="text-sm text-
|
|
83
|
+
class="text-sm text-[var(--ks-fg,#ffffff)] font-medium flex items-center gap-2"
|
|
84
84
|
data-testid="camera-capture-title"
|
|
85
85
|
>
|
|
86
86
|
<Camera size={16} /> Camera
|
|
87
87
|
</span>
|
|
88
88
|
<button
|
|
89
89
|
onClick={() => props.onClose()}
|
|
90
|
-
class="text-
|
|
90
|
+
class="text-[var(--ks-fg-subtle,#71717a)] hover:text-[var(--ks-fg-muted,#a1a1aa)] cursor-pointer"
|
|
91
91
|
aria-label="Close camera"
|
|
92
92
|
>
|
|
93
93
|
<X size={18} />
|
|
@@ -96,7 +96,7 @@ export default function CameraCapture(props: Props) {
|
|
|
96
96
|
|
|
97
97
|
<Show when={error()}>
|
|
98
98
|
<div class="p-6 text-center">
|
|
99
|
-
<p class="text-sm text-
|
|
99
|
+
<p class="text-sm text-[var(--ks-danger-fg,#f87171)] mb-3">{error()}</p>
|
|
100
100
|
<Button intent="secondary" variant="ghost" onClick={() => props.onClose()}>
|
|
101
101
|
Close
|
|
102
102
|
</Button>
|
|
@@ -104,7 +104,7 @@ export default function CameraCapture(props: Props) {
|
|
|
104
104
|
</Show>
|
|
105
105
|
|
|
106
106
|
<Show when={!error()}>
|
|
107
|
-
<div class="relative bg-
|
|
107
|
+
<div class="relative bg-[var(--ks-surface,#0f0f0f)] aspect-video">
|
|
108
108
|
<Show when={!captured()}>
|
|
109
109
|
<video ref={videoRef} autoplay playsinline muted class="w-full h-full object-cover" />
|
|
110
110
|
</Show>
|
|
@@ -131,7 +131,7 @@ export default function CameraCapture(props: Props) {
|
|
|
131
131
|
<button
|
|
132
132
|
type="button"
|
|
133
133
|
onClick={takePhoto}
|
|
134
|
-
class="w-14 h-14 rounded-full border-4 border-
|
|
134
|
+
class="w-14 h-14 rounded-full border-4 border-[var(--ks-border-strong,#3f3f46)] bg-[var(--ks-fg,#ffffff)] hover:bg-[var(--ks-fg,#ffffff)] active:scale-95 transition-all cursor-pointer"
|
|
135
135
|
title="Take photo"
|
|
136
136
|
aria-label="Take photo"
|
|
137
137
|
/>
|
|
@@ -11,7 +11,7 @@ interface ChartLegendProps {
|
|
|
11
11
|
label: string;
|
|
12
12
|
/** Pre-formatted value string (e.g. a currency string). */
|
|
13
13
|
value: string;
|
|
14
|
-
/** Optional Tailwind text color class for the value. Defaults to
|
|
14
|
+
/** Optional Tailwind text color class for the value. Defaults to var(--ks-fg). */
|
|
15
15
|
valueColor?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -20,11 +20,11 @@ export default function ChartLegend(props: ChartLegendProps) {
|
|
|
20
20
|
<div class="flex items-center gap-2">
|
|
21
21
|
<span class={`w-2 h-2 rounded-sm ${props.dot}`} />
|
|
22
22
|
<div class="flex flex-col">
|
|
23
|
-
<span class="text-[9px] uppercase tracking-widest text-
|
|
23
|
+
<span class="text-[9px] uppercase tracking-widest text-[var(--ks-fg-subtle,#71717a)] font-medium leading-tight">
|
|
24
24
|
{props.label}
|
|
25
25
|
</span>
|
|
26
26
|
<span
|
|
27
|
-
class={`text-xs font-bold tabular-nums leading-tight ${props.valueColor || "text-
|
|
27
|
+
class={`text-xs font-bold tabular-nums leading-tight ${props.valueColor || "text-[var(--ks-fg,#ffffff)]"}`}
|
|
28
28
|
>
|
|
29
29
|
{props.value}
|
|
30
30
|
</span>
|
|
@@ -44,7 +44,7 @@ export default function CopyButton(props: CopyButtonProps): JSX.Element {
|
|
|
44
44
|
type="button"
|
|
45
45
|
onClick={handleClick}
|
|
46
46
|
aria-label={props.ariaLabel ?? "Copy"}
|
|
47
|
-
class={`ks-interactive px-2 py-1.5 rounded bg-
|
|
47
|
+
class={`ks-interactive px-2 py-1.5 rounded bg-[var(--ks-surface-raised,#1a1a1a)] border border-[var(--ks-border-strong,#3f3f46)] text-[var(--ks-fg-muted,#a1a1aa)] hover:text-[var(--ks-fg,#ffffff)] text-xs flex items-center gap-1 ${props.class ?? ""}`}
|
|
48
48
|
>
|
|
49
49
|
{copied() ? <Check size={props.size ?? 12} /> : <Copy size={props.size ?? 12} />}
|
|
50
50
|
{copied() ? (props.copiedLabel ?? "Copied") : (props.label ?? "Copy")}
|
|
@@ -46,6 +46,7 @@ import ChevronDown from "lucide-solid/icons/chevron-down";
|
|
|
46
46
|
import ChevronUp from "lucide-solid/icons/chevron-up";
|
|
47
47
|
import ChevronsUpDown from "lucide-solid/icons/chevrons-up-down";
|
|
48
48
|
import DatePicker from "./DatePicker";
|
|
49
|
+
import { injectCSS } from "../../utils/inject-css";
|
|
49
50
|
|
|
50
51
|
// ---------------------------------------------------------------------------
|
|
51
52
|
// Injected CSS
|
|
@@ -58,37 +59,37 @@ const STYLE_ID = "ksui-datatable-style";
|
|
|
58
59
|
// ---------------------------------------------------------------------------
|
|
59
60
|
//
|
|
60
61
|
// Every color in the injected stylesheet is driven by a `--ksui-dt-*` CSS
|
|
61
|
-
// custom property;
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
62
|
+
// custom property; each one now falls through to a `--ks-*` design token
|
|
63
|
+
// (THEME-SPEC §1.2) before the literal dark-theme fallback, so a themed host
|
|
64
|
+
// restyles the table without any ksui code change. To retint further, wrap
|
|
65
|
+
// the table in a container that sets the `--ksui-dt-*` vars directly — they
|
|
66
|
+
// still win over the token layer per the old-name-stays-the-read-point rule.
|
|
65
67
|
//
|
|
66
|
-
// Full list of overridable vars (
|
|
67
|
-
// --ksui-dt-card-bg outer card background
|
|
68
|
-
//
|
|
69
|
-
// --ksui-dt-
|
|
70
|
-
// --ksui-dt-border
|
|
71
|
-
// --ksui-dt-
|
|
72
|
-
// --ksui-dt-control-bg filter button / menu / select / search / show-more bg (zinc-900, #18181b)
|
|
68
|
+
// Full list of overridable vars (role -> token):
|
|
69
|
+
// --ksui-dt-card-bg outer card background -> --ks-surface / --ks-surface-raised gradient
|
|
70
|
+
// --ksui-dt-radius outer card / control corner radius (0.625rem, not a color)
|
|
71
|
+
// --ksui-dt-border card / header / footer / control border -> --ks-border
|
|
72
|
+
// --ksui-dt-row-border row divider + expansion-row top border -> --ks-border-subtle
|
|
73
|
+
// --ksui-dt-control-bg filter button / menu / select / search / show-more bg -> --ks-input-bg
|
|
73
74
|
// (the date filter is the ksui DatePicker; it reads the same vars)
|
|
74
|
-
// --ksui-dt-fg primary text: row/search text
|
|
75
|
-
// --ksui-dt-fg-strong hover-to-full-contrast text on controls/pager
|
|
76
|
-
// --ksui-dt-text secondary text: filter btn / select / pager nums+arrows
|
|
77
|
-
// --ksui-dt-text-strong show-more label + sortable-header hover
|
|
78
|
-
// --ksui-dt-muted header th / search icon+placeholder / empty / info text
|
|
79
|
-
// --ksui-dt-faint sort caret / per-row date separator / pager ellipsis
|
|
80
|
-
// --ksui-dt-row-hover row hover + pager hover bg
|
|
81
|
-
// --ksui-dt-row-active row :active bg
|
|
82
|
-
// --ksui-dt-expansion-bg per-row expansion panel bg
|
|
83
|
-
// --ksui-dt-skeleton loading skeleton shimmer bg
|
|
84
|
-
// --ksui-dt-accent active accent text: active pager num / show-more hover
|
|
85
|
-
// --ksui-dt-accent-bg active pager num bg
|
|
86
|
-
// --ksui-dt-accent-border focus/hover accent border: search focus, show-more hover
|
|
87
|
-
// --ksui-dt-good positive-status badge text, e.g. "Active"
|
|
88
|
-
// --ksui-dt-good-bg positive-status badge bg
|
|
89
|
-
// --ksui-dt-danger negative-status badge text, e.g. "Overdue"/"Expired"
|
|
90
|
-
// --ksui-dt-danger-bg negative-status badge bg
|
|
91
|
-
// --ksui-dt-thead-bg header-row tint, layered over card-bg to set it apart from data rows
|
|
75
|
+
// --ksui-dt-fg primary text: row/search text -> --ks-fg
|
|
76
|
+
// --ksui-dt-fg-strong hover-to-full-contrast text on controls/pager -> --ks-fg
|
|
77
|
+
// --ksui-dt-text secondary text: filter btn / select / pager nums+arrows -> --ks-fg-muted
|
|
78
|
+
// --ksui-dt-text-strong show-more label + sortable-header hover -> --ks-fg
|
|
79
|
+
// --ksui-dt-muted header th / search icon+placeholder / empty / info text -> --ks-fg-subtle
|
|
80
|
+
// --ksui-dt-faint sort caret / per-row date separator / pager ellipsis -> --ks-fg-subtle
|
|
81
|
+
// --ksui-dt-row-hover row hover + pager hover bg -> --ks-border
|
|
82
|
+
// --ksui-dt-row-active row :active bg -> --ks-border-strong (color-mix)
|
|
83
|
+
// --ksui-dt-expansion-bg per-row expansion panel bg -> --ks-bg (color-mix)
|
|
84
|
+
// --ksui-dt-skeleton loading skeleton shimmer bg -> --ks-border
|
|
85
|
+
// --ksui-dt-accent active accent text: active pager num / show-more hover -> --ks-accent
|
|
86
|
+
// --ksui-dt-accent-bg active pager num bg -> --ks-accent (color-mix)
|
|
87
|
+
// --ksui-dt-accent-border focus/hover accent border: search focus, show-more hover -> --ks-focus-ring (color-mix)
|
|
88
|
+
// --ksui-dt-good positive-status badge text, e.g. "Active" -> --ks-success-fg
|
|
89
|
+
// --ksui-dt-good-bg positive-status badge bg -> --ks-success-fg (color-mix)
|
|
90
|
+
// --ksui-dt-danger negative-status badge text, e.g. "Overdue"/"Expired" -> --ks-danger-fg
|
|
91
|
+
// --ksui-dt-danger-bg negative-status badge bg -> --ks-danger-fg (color-mix)
|
|
92
|
+
// --ksui-dt-thead-bg header-row tint, layered over card-bg to set it apart from data rows -> --ks-fg (color-mix)
|
|
92
93
|
//
|
|
93
94
|
// `.ksui-datatable-td-num` / `.ksui-datatable-th-num` (mono, tabular-nums,
|
|
94
95
|
// right-aligned, nowrap) and `.ksui-datatable-badge` (+ `-ok` / `-warn` /
|
|
@@ -97,72 +98,67 @@ const STYLE_ID = "ksui-datatable-style";
|
|
|
97
98
|
// infers column semantics from data.
|
|
98
99
|
//
|
|
99
100
|
const DATATABLE_CSS = `
|
|
100
|
-
.ksui-datatable{background:var(--ksui-dt-card-bg,linear-gradient(135deg,#0f0f0f 0
|
|
101
|
-
.ksui-datatable-header{display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem;border-bottom:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));padding:0.75rem 0.875rem;}
|
|
101
|
+
.ksui-datatable{background:var(--ksui-dt-card-bg,linear-gradient(135deg,var(--ks-surface,#0f0f0f) 0%,var(--ks-surface-raised,#1a1a1a) 100%));border:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));border-radius:var(--ksui-dt-radius,0.625rem);color:var(--ksui-dt-fg,var(--ks-fg,#ffffff));box-shadow:0 1px 2px rgba(0,0,0,0.3),0 8px 20px -10px rgba(0,0,0,0.5);}
|
|
102
|
+
.ksui-datatable-header{display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem;border-bottom:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));padding:0.75rem 0.875rem;}
|
|
102
103
|
.ksui-datatable-filters-inline{display:none;flex:1 1 0%;}
|
|
103
104
|
.ksui-datatable-filters-mobile{display:block;flex:1 1 0%;}
|
|
104
105
|
@media (min-width:768px){.ksui-datatable-filters-inline{display:block;}.ksui-datatable-filters-mobile{display:none;}}
|
|
105
106
|
.ksui-datatable-spacer{flex:1 1 0%;}
|
|
106
107
|
.ksui-datatable-controls{display:flex;align-items:center;gap:0.5rem;}
|
|
107
108
|
.ksui-datatable-filter-toggle-wrap{position:relative;}
|
|
108
|
-
.ksui-datatable-filter-toggle{display:inline-flex;cursor:pointer;align-items:center;gap:0.5rem;border-radius:0.5rem;border:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));background:var(--ksui-dt-control-bg,#18181b);padding:0.5rem 0.75rem;font-size:0.75rem;line-height:1rem;color:var(--ksui-dt-text,#a1a1aa);transition:color 0.15s ease;}
|
|
109
|
-
.ksui-datatable-filter-toggle:hover{color:var(--ksui-dt-fg-strong,#ffffff);}
|
|
110
|
-
.ksui-datatable-filter-menu{position:absolute;left:0;top:100%;z-index:50;margin-top:0.5rem;border-radius:0.5rem;border:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));background:var(--ksui-dt-control-bg,#18181b);padding:0.75rem;box-shadow:0 20px 25px -5px rgba(0,0,0,0.4),0 8px 10px -6px rgba(0,0,0,0.4);}
|
|
111
|
-
.ksui-datatable-select{border-radius:0.375rem;border:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));background:var(--ksui-dt-control-bg,#18181b);padding:0.5rem;font-size:0.75rem;line-height:1rem;color:var(--ksui-dt-text,#a1a1aa);}
|
|
109
|
+
.ksui-datatable-filter-toggle{display:inline-flex;cursor:pointer;align-items:center;gap:0.5rem;border-radius:0.5rem;border:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));background:var(--ksui-dt-control-bg,var(--ks-input-bg,#18181b));padding:0.5rem 0.75rem;font-size:0.75rem;line-height:1rem;color:var(--ksui-dt-text,var(--ks-fg-muted,#a1a1aa));transition:color 0.15s ease;}
|
|
110
|
+
.ksui-datatable-filter-toggle:hover{color:var(--ksui-dt-fg-strong,var(--ks-fg,#ffffff));}
|
|
111
|
+
.ksui-datatable-filter-menu{position:absolute;left:0;top:100%;z-index:50;margin-top:0.5rem;border-radius:0.5rem;border:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));background:var(--ksui-dt-control-bg,var(--ks-input-bg,#18181b));padding:0.75rem;box-shadow:0 20px 25px -5px rgba(0,0,0,0.4),0 8px 10px -6px rgba(0,0,0,0.4);}
|
|
112
|
+
.ksui-datatable-select{border-radius:0.375rem;border:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));background:var(--ksui-dt-control-bg,var(--ks-input-bg,#18181b));padding:0.5rem;font-size:0.75rem;line-height:1rem;color:var(--ksui-dt-text,var(--ks-fg-muted,#a1a1aa));}
|
|
112
113
|
.ksui-datatable-search-wrap{position:relative;}
|
|
113
|
-
.ksui-datatable-search-icon{position:absolute;left:0.75rem;top:50%;transform:translateY(-50%);color:var(--ksui-dt-muted,#71717a);pointer-events:none;}
|
|
114
|
-
.ksui-datatable-search-input{width:100%;border-radius:0.5rem;border:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));background:var(--ksui-dt-control-bg,#18181b);padding:0.5rem 1rem 0.5rem 2.25rem;font-size:0.875rem;line-height:1.25rem;color:var(--ksui-dt-fg,#
|
|
115
|
-
.ksui-datatable-search-input::placeholder{color:var(--ksui-dt-muted,#71717a);}
|
|
116
|
-
.ksui-datatable-search-input:focus{border-color:var(--ksui-dt-accent-border,
|
|
114
|
+
.ksui-datatable-search-icon{position:absolute;left:0.75rem;top:50%;transform:translateY(-50%);color:var(--ksui-dt-muted,var(--ks-fg-subtle,#71717a));pointer-events:none;}
|
|
115
|
+
.ksui-datatable-search-input{width:100%;border-radius:0.5rem;border:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));background:var(--ksui-dt-control-bg,var(--ks-input-bg,#18181b));padding:0.5rem 1rem 0.5rem 2.25rem;font-size:0.875rem;line-height:1.25rem;color:var(--ksui-dt-fg,var(--ks-fg,#ffffff));outline:none;transition:border-color 0.15s ease;}
|
|
116
|
+
.ksui-datatable-search-input::placeholder{color:var(--ksui-dt-muted,var(--ks-fg-subtle,#71717a));}
|
|
117
|
+
.ksui-datatable-search-input:focus{border-color:var(--ksui-dt-accent-border,color-mix(in srgb, var(--ks-focus-ring, #c9a961) 40%, transparent));}
|
|
117
118
|
@media (min-width:640px){.ksui-datatable-search-input{width:18rem;}}
|
|
118
119
|
.ksui-datatable-scroll{overflow-x:auto;transition:opacity 0.15s ease;}
|
|
119
120
|
.ksui-datatable-table{width:100%;text-align:left;font-size:0.78125rem;line-height:1.3;border-collapse:collapse;}
|
|
120
|
-
.ksui-datatable-thead{border-bottom:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));background:var(--ksui-dt-thead-bg,
|
|
121
|
-
.ksui-datatable-th{padding:0.5625rem 0.875rem;background:var(--ksui-dt-thead-bg,
|
|
121
|
+
.ksui-datatable-thead{border-bottom:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));background:var(--ksui-dt-thead-bg,color-mix(in srgb, var(--ks-fg, #ffffff) 3%, transparent));font-family:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;font-size:0.65625rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--ksui-dt-muted,var(--ks-fg-subtle,#71717a));}
|
|
122
|
+
.ksui-datatable-th{padding:0.5625rem 0.875rem;background:var(--ksui-dt-thead-bg,color-mix(in srgb, var(--ks-fg, #ffffff) 3%, transparent));white-space:nowrap;}
|
|
122
123
|
.ksui-datatable-th-num{text-align:right;}
|
|
123
124
|
.ksui-datatable-th-sortable{cursor:pointer;transition:color 0.15s ease;}
|
|
124
|
-
.ksui-datatable-th-sortable:hover{color:var(--ksui-dt-text-strong,#
|
|
125
|
+
.ksui-datatable-th-sortable:hover{color:var(--ksui-dt-text-strong,var(--ks-fg,#ffffff));}
|
|
125
126
|
.ksui-datatable-th-inner{display:inline-flex;align-items:center;}
|
|
126
|
-
.ksui-datatable-sort-icon{margin-left:0.25rem;display:inline-flex;color:var(--ksui-dt-faint,#
|
|
127
|
-
.ksui-datatable-row{border-top:1px solid var(--ksui-dt-row-border,rgba(39,39,42,0.
|
|
128
|
-
.ksui-datatable-row:hover{background-color:var(--ksui-dt-row-hover,rgba(39,39,42,0.5));}
|
|
127
|
+
.ksui-datatable-sort-icon{margin-left:0.25rem;display:inline-flex;color:var(--ksui-dt-faint,var(--ks-fg-subtle,#71717a));}
|
|
128
|
+
.ksui-datatable-row{border-top:1px solid var(--ksui-dt-row-border,var(--ks-border-subtle,rgba(39,39,42,0.25)));transition:background-color 0.15s ease;}
|
|
129
|
+
.ksui-datatable-row:hover{background-color:var(--ksui-dt-row-hover,var(--ks-border,rgba(39,39,42,0.5)));}
|
|
129
130
|
.ksui-datatable-row-clickable{cursor:pointer;}
|
|
130
|
-
.ksui-datatable-row-clickable:active{background-color:var(--ksui-dt-row-active,
|
|
131
|
+
.ksui-datatable-row-clickable:active{background-color:var(--ksui-dt-row-active,color-mix(in srgb, var(--ks-border-strong, #3f3f46) 70%, transparent));}
|
|
131
132
|
.ksui-datatable-td{padding:0.5rem 0.875rem;vertical-align:middle;white-space:nowrap;}
|
|
132
|
-
.ksui-datatable-td-num{font-family:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap;color:var(--ksui-dt-text-strong,#
|
|
133
|
-
.ksui-datatable-code{display:block;font-family:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;font-size:0.71875rem;color:var(--ksui-dt-fg,#
|
|
134
|
-
.ksui-datatable-badge{display:inline-block;font-family:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;font-size:0.625rem;padding:0.125rem 0.4375rem;border-radius:0.3125rem;letter-spacing:0.02em;background:var(--ksui-dt-control-bg,#18181b);color:var(--ksui-dt-text,#a1a1aa);}
|
|
135
|
-
.ksui-datatable-badge-ok{background:var(--ksui-dt-good-bg,
|
|
136
|
-
.ksui-datatable-badge-warn{background:var(--ksui-dt-accent-bg,
|
|
137
|
-
.ksui-datatable-badge-danger{background:var(--ksui-dt-danger-bg,
|
|
138
|
-
.ksui-datatable-expansion-row{border-top:1px solid var(--ksui-dt-row-border,rgba(39,39,42,0.
|
|
133
|
+
.ksui-datatable-td-num{font-family:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap;color:var(--ksui-dt-text-strong,var(--ks-fg,#ffffff));}
|
|
134
|
+
.ksui-datatable-code{display:block;font-family:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;font-size:0.71875rem;color:var(--ksui-dt-fg,var(--ks-fg,#ffffff));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:34ch;}
|
|
135
|
+
.ksui-datatable-badge{display:inline-block;font-family:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;font-size:0.625rem;padding:0.125rem 0.4375rem;border-radius:0.3125rem;letter-spacing:0.02em;background:var(--ksui-dt-control-bg,var(--ks-input-bg,#18181b));color:var(--ksui-dt-text,var(--ks-fg-muted,#a1a1aa));}
|
|
136
|
+
.ksui-datatable-badge-ok{background:var(--ksui-dt-good-bg,color-mix(in srgb, var(--ks-success-fg, #34d399) 14%, transparent));color:var(--ksui-dt-good,var(--ks-success-fg,#34d399));}
|
|
137
|
+
.ksui-datatable-badge-warn{background:var(--ksui-dt-accent-bg,color-mix(in srgb, var(--ks-accent, #fbbf24) 20%, transparent));color:var(--ksui-dt-accent,var(--ks-accent,#fbbf24));}
|
|
138
|
+
.ksui-datatable-badge-danger{background:var(--ksui-dt-danger-bg,color-mix(in srgb, var(--ks-danger-fg, #f87171) 14%, transparent));color:var(--ksui-dt-danger,var(--ks-danger-fg,#f87171));}
|
|
139
|
+
.ksui-datatable-expansion-row{border-top:1px solid var(--ksui-dt-row-border,var(--ks-border-subtle,rgba(39,39,42,0.25)));background-color:var(--ksui-dt-expansion-bg,color-mix(in srgb, var(--ks-bg, #0a0a0a) 40%, transparent));}
|
|
139
140
|
.ksui-datatable-expansion-td{padding:0;}
|
|
140
|
-
.ksui-datatable-skeleton{height:1rem;width:100%;border-radius:0.25rem;background:var(--ksui-dt-skeleton,rgba(39,39,42,0.5));animation:ksuiDatatablePulse 1.5s cubic-bezier(0.4,0,0.6,1) infinite;}
|
|
141
|
+
.ksui-datatable-skeleton{height:1rem;width:100%;border-radius:0.25rem;background:var(--ksui-dt-skeleton,var(--ks-border,rgba(39,39,42,0.5)));animation:ksuiDatatablePulse 1.5s cubic-bezier(0.4,0,0.6,1) infinite;}
|
|
141
142
|
@keyframes ksuiDatatablePulse{0%,100%{opacity:1;}50%{opacity:0.5;}}
|
|
142
|
-
.ksui-datatable-empty{padding:3rem 1rem;text-align:center;color:var(--ksui-dt-muted,#71717a);}
|
|
143
|
-
.ksui-datatable-footer{display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));padding:0.625rem 0.875rem;}
|
|
144
|
-
.ksui-datatable-info{font-size:0.75rem;color:var(--ksui-dt-muted,#71717a);}
|
|
145
|
-
.ksui-datatable-showmore{border-radius:0.5rem;border:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));background:var(--ksui-dt-control-bg,#18181b);padding:0.5rem 1rem;font-size:0.75rem;font-weight:500;color:var(--ksui-dt-text-strong,#
|
|
146
|
-
.ksui-datatable-showmore:hover:not(:disabled){border-color:var(--ksui-dt-accent-border,
|
|
143
|
+
.ksui-datatable-empty{padding:3rem 1rem;text-align:center;color:var(--ksui-dt-muted,var(--ks-fg-subtle,#71717a));}
|
|
144
|
+
.ksui-datatable-footer{display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));padding:0.625rem 0.875rem;}
|
|
145
|
+
.ksui-datatable-info{font-size:0.75rem;color:var(--ksui-dt-muted,var(--ks-fg-subtle,#71717a));}
|
|
146
|
+
.ksui-datatable-showmore{border-radius:0.5rem;border:1px solid var(--ksui-dt-border,var(--ks-border,rgba(39,39,42,0.5)));background:var(--ksui-dt-control-bg,var(--ks-input-bg,#18181b));padding:0.5rem 1rem;font-size:0.75rem;font-weight:500;color:var(--ksui-dt-text-strong,var(--ks-fg,#ffffff));transition:border-color 0.15s ease,color 0.15s ease;cursor:pointer;}
|
|
147
|
+
.ksui-datatable-showmore:hover:not(:disabled){border-color:var(--ksui-dt-accent-border,color-mix(in srgb, var(--ks-focus-ring, #c9a961) 40%, transparent));color:var(--ksui-dt-accent,var(--ks-accent,#fbbf24));}
|
|
147
148
|
.ksui-datatable-showmore:disabled{cursor:not-allowed;opacity:0.5;}
|
|
148
149
|
.ksui-datatable-pager{display:flex;align-items:center;gap:0.25rem;}
|
|
149
|
-
.ksui-datatable-pager-arrow{border-radius:0.25rem;padding:0.375rem;color:var(--ksui-dt-text,#a1a1aa);transition:background-color 0.15s ease,color 0.15s ease;background:transparent;border:0;cursor:pointer;display:inline-flex;}
|
|
150
|
-
.ksui-datatable-pager-arrow:hover:not(:disabled){background-color:var(--ksui-dt-row-hover,rgba(39,39,42,0.5));color:var(--ksui-dt-fg-strong,#ffffff);}
|
|
150
|
+
.ksui-datatable-pager-arrow{border-radius:0.25rem;padding:0.375rem;color:var(--ksui-dt-text,var(--ks-fg-muted,#a1a1aa));transition:background-color 0.15s ease,color 0.15s ease;background:transparent;border:0;cursor:pointer;display:inline-flex;}
|
|
151
|
+
.ksui-datatable-pager-arrow:hover:not(:disabled){background-color:var(--ksui-dt-row-hover,var(--ks-border,rgba(39,39,42,0.5)));color:var(--ksui-dt-fg-strong,var(--ks-fg,#ffffff));}
|
|
151
152
|
.ksui-datatable-pager-arrow:disabled{cursor:not-allowed;opacity:0.3;}
|
|
152
|
-
.ksui-datatable-pager-num{border-radius:0.25rem;padding:0.25rem 0.625rem;font-size:0.75rem;color:var(--ksui-dt-text,#a1a1aa);transition:background-color 0.15s ease,color 0.15s ease;background:transparent;border:0;cursor:pointer;}
|
|
153
|
-
.ksui-datatable-pager-num:hover{background-color:var(--ksui-dt-row-hover,rgba(39,39,42,0.5));color:var(--ksui-dt-fg-strong,#ffffff);}
|
|
154
|
-
.ksui-datatable-pager-num-active{background-color:var(--ksui-dt-accent-bg,
|
|
155
|
-
.ksui-datatable-pager-num-active:hover{background-color:var(--ksui-dt-accent-bg,
|
|
156
|
-
.ksui-datatable-pager-ellipsis{padding:0 0.375rem;font-size:0.75rem;color:var(--ksui-dt-faint,#
|
|
153
|
+
.ksui-datatable-pager-num{border-radius:0.25rem;padding:0.25rem 0.625rem;font-size:0.75rem;color:var(--ksui-dt-text,var(--ks-fg-muted,#a1a1aa));transition:background-color 0.15s ease,color 0.15s ease;background:transparent;border:0;cursor:pointer;}
|
|
154
|
+
.ksui-datatable-pager-num:hover{background-color:var(--ksui-dt-row-hover,var(--ks-border,rgba(39,39,42,0.5)));color:var(--ksui-dt-fg-strong,var(--ks-fg,#ffffff));}
|
|
155
|
+
.ksui-datatable-pager-num-active{background-color:var(--ksui-dt-accent-bg,color-mix(in srgb, var(--ks-accent, #fbbf24) 20%, transparent));font-weight:500;color:var(--ksui-dt-accent,var(--ks-accent,#fbbf24));}
|
|
156
|
+
.ksui-datatable-pager-num-active:hover{background-color:var(--ksui-dt-accent-bg,color-mix(in srgb, var(--ks-accent, #fbbf24) 20%, transparent));color:var(--ksui-dt-accent,var(--ks-accent,#fbbf24));}
|
|
157
|
+
.ksui-datatable-pager-ellipsis{padding:0 0.375rem;font-size:0.75rem;color:var(--ksui-dt-faint,var(--ks-fg-subtle,#71717a));}
|
|
157
158
|
`;
|
|
158
159
|
|
|
159
160
|
function ensureDataTableStyle(): void {
|
|
160
|
-
|
|
161
|
-
if (document.getElementById(STYLE_ID)) return;
|
|
162
|
-
const el = document.createElement("style");
|
|
163
|
-
el.id = STYLE_ID;
|
|
164
|
-
el.textContent = DATATABLE_CSS;
|
|
165
|
-
document.head.appendChild(el);
|
|
161
|
+
injectCSS(STYLE_ID, DATATABLE_CSS);
|
|
166
162
|
}
|
|
167
163
|
|
|
168
164
|
// ---------------------------------------------------------------------------
|