@kahitsan/ksui 0.32.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.
Files changed (65) hide show
  1. package/package.json +4 -1
  2. package/src/components/base/AccountAvatar.tsx +1 -1
  3. package/src/components/base/AddAttachmentTile.tsx +4 -4
  4. package/src/components/base/BadgeSelect.tsx +10 -10
  5. package/src/components/base/Button.tsx +70 -38
  6. package/src/components/base/CameraCapture.tsx +8 -8
  7. package/src/components/base/ChartLegend.tsx +3 -3
  8. package/src/components/base/CopyButton.tsx +1 -1
  9. package/src/components/base/DataTable.tsx +68 -72
  10. package/src/components/base/DatePicker.tsx +77 -82
  11. package/src/components/base/DateTile.tsx +7 -7
  12. package/src/components/base/DetailRow.tsx +2 -2
  13. package/src/components/base/Dropdown.tsx +15 -15
  14. package/src/components/base/ExistingAttachmentTile.tsx +6 -6
  15. package/src/components/base/EyebrowBadge.tsx +15 -15
  16. package/src/components/base/FormErrorBanner.tsx +1 -1
  17. package/src/components/base/FormField.tsx +2 -2
  18. package/src/components/base/ImageCropper.tsx +19 -19
  19. package/src/components/base/ImageViewer.tsx +7 -11
  20. package/src/components/base/KpiCard.tsx +7 -7
  21. package/src/components/base/Modal.tsx +10 -14
  22. package/src/components/base/ProgressBar.tsx +63 -59
  23. package/src/components/base/RadioCardGroup.tsx +13 -6
  24. package/src/components/base/SectionHeading.tsx +4 -4
  25. package/src/components/base/SegmentedFilter.tsx +60 -4
  26. package/src/components/base/SocialLinksBar.tsx +3 -15
  27. package/src/components/base/StatusIndicator.tsx +11 -11
  28. package/src/components/base/StatusPill.tsx +15 -15
  29. package/src/components/base/TagPill.tsx +1 -1
  30. package/src/components/base/ThemeToggle.tsx +12 -20
  31. package/src/components/base/Tooltip.tsx +2 -2
  32. package/src/components/composite/AccountRadioPicker.tsx +8 -4
  33. package/src/components/composite/ComboBox.tsx +51 -44
  34. package/src/components/composite/CustomRenderer.tsx +6 -13
  35. package/src/components/composite/FileField.tsx +10 -17
  36. package/src/components/composite/FlowGraph.tsx +29 -36
  37. package/src/components/composite/FlowRunner.tsx +11 -18
  38. package/src/components/composite/FormAdvancedSection.tsx +31 -31
  39. package/src/components/composite/LiveTimer.tsx +3 -3
  40. package/src/components/composite/MarkdownNotes.tsx +11 -11
  41. package/src/components/composite/MentionTextarea.tsx +10 -10
  42. package/src/components/composite/NotFound.tsx +7 -7
  43. package/src/components/composite/PaymentAccountPicker.tsx +19 -19
  44. package/src/components/composite/SalesBodyEditor.tsx +26 -26
  45. package/src/components/composite/SearchableSelect.tsx +14 -14
  46. package/src/components/composite/SecretReveal.tsx +5 -5
  47. package/src/components/composite/TransactionAccountFields.tsx +78 -0
  48. package/src/components/composite/TransactionForm.tsx +83 -169
  49. package/src/components/composite/TransactionPayableFields.tsx +97 -0
  50. package/src/components/composite/TransferAccountsPicker.tsx +20 -20
  51. package/src/components/composite/TransferFeeChip.tsx +2 -2
  52. package/src/components/composite/VoucherPicker.tsx +25 -25
  53. package/src/components/composite/resource/ResourceForm.tsx +1 -1
  54. package/src/components/composite/resource/ResourcePage.tsx +8 -8
  55. package/src/components/composite/resource/cells.tsx +6 -3
  56. package/src/components/composite/resource/form-spec.ts +141 -0
  57. package/src/components/composite/resource/route-adapter.ts +63 -0
  58. package/src/components/composite/resource/route-spec.test.ts +125 -0
  59. package/src/components/composite/resource/route-spec.ts +158 -0
  60. package/src/index.ts +8 -0
  61. package/src/utils/INPUT_CLASS.ts +6 -3
  62. package/src/utils/account-icons.ts +29 -7
  63. package/src/utils/highlight.tsx +5 -6
  64. package/src/utils/inject-css.ts +13 -0
  65. package/tailwind.js +11 -7
@@ -0,0 +1,125 @@
1
+ // Unit tests for the RouteSpec builder layer + its lowering. They pin the
2
+ // contract that a built RouteSpec lowers to the EXACT ResourceUiSpec a
3
+ // hand-authored spec would produce, and that unrepresentable config fails
4
+ // loudly instead of being silently dropped.
5
+ import { describe, it, expect } from "vitest";
6
+ import { Cell, action, col, defineRoute, setting, table } from "./route-spec";
7
+ import { defineForm, field, fieldToUiField } from "./form-spec";
8
+ import { routeToResourceSpec } from "./route-adapter";
9
+ import type { ResourceUiSpec } from "./spec";
10
+
11
+ const LABELS = {
12
+ add: "Add Vendor",
13
+ createTitle: "New Vendor",
14
+ createSubmit: "Create",
15
+ editTitle: "Edit Vendor",
16
+ editSubmit: "Save",
17
+ titleField: "name",
18
+ searchPlaceholder: "Search vendors",
19
+ empty: "No vendors yet.",
20
+ noResults: "No vendors match.",
21
+ createErrorFallback: "Could not create the vendor.",
22
+ updateErrorFallback: "Could not update the vendor.",
23
+ networkError: "Network error — try again.",
24
+ archiveTitle: "Archive vendor?",
25
+ archiveMessage: "It can be restored later.",
26
+ archiveConfirm: "Archive",
27
+ } as const;
28
+
29
+ function vendorRoute() {
30
+ return defineRoute({
31
+ title: "Vendors",
32
+ basePath: "/api/vendors",
33
+ softDeleteField: "is_active",
34
+ testIdPrefix: "vendor",
35
+ permissions: { view: "vendors.view", edit: ["vendors.edit"], delete: "vendors.delete" },
36
+ header: {
37
+ actions: [action("add", { label: "Add Vendor", flow: "vendors.create" })],
38
+ },
39
+ view: table({
40
+ columns: [
41
+ col("name", { title: "Name", orderable: true, render: Cell.Title }),
42
+ col("kind", { title: "Kind", render: Cell.Enum({ a: "A", b: "B" }) }),
43
+ col("notes", { title: "Notes", render: Cell.Text({ muted: true }) }),
44
+ ],
45
+ }),
46
+ form: defineForm({
47
+ fields: {
48
+ name: field.text({ label: "Name", required: true, transform: "trim" }),
49
+ notes: field.textarea({ label: "Notes", transform: "trimOrNull", rows: 3 }),
50
+ kind: field.select({
51
+ label: "Kind",
52
+ default: "a",
53
+ options: [
54
+ { value: "a", label: "A" },
55
+ { value: "b", label: "B" },
56
+ ],
57
+ }),
58
+ },
59
+ submit: { create: "vendors.create", update: "vendors.update" },
60
+ }),
61
+ detail: [{ label: "Name", value: { type: "field", key: "name" } }],
62
+ settings: { pageSize: setting.number({ default: 25 }) },
63
+ labels: LABELS,
64
+ });
65
+ }
66
+
67
+ describe("routeToResourceSpec lowering", () => {
68
+ it("lowers a built route to the hand-authored ResourceUiSpec shape", () => {
69
+ const lowered = routeToResourceSpec(vendorRoute());
70
+ const hand: ResourceUiSpec = {
71
+ basePath: "/api/vendors",
72
+ title: "Vendors",
73
+ permissions: { view: "vendors.view", edit: ["vendors.edit"], delete: "vendors.delete" },
74
+ softDeleteField: "is_active",
75
+ testIdPrefix: "vendor",
76
+ columns: [
77
+ { key: "name", title: "Name", orderable: true, render: { type: "title" } },
78
+ { key: "kind", title: "Kind", render: { type: "enum", labels: { a: "A", b: "B" } } },
79
+ { key: "notes", title: "Notes", render: { type: "text", muted: true } },
80
+ ],
81
+ fields: [
82
+ { key: "name", label: "Name", type: "text", required: true, transform: "trim" },
83
+ { key: "notes", label: "Notes", type: "textarea", transform: "trimOrNull", rows: 3 },
84
+ {
85
+ key: "kind",
86
+ label: "Kind",
87
+ type: "select",
88
+ default: "a",
89
+ options: [
90
+ { value: "a", label: "A" },
91
+ { value: "b", label: "B" },
92
+ ],
93
+ },
94
+ ],
95
+ detail: [{ label: "Name", value: { type: "field", key: "name" } }],
96
+ labels: LABELS,
97
+ };
98
+ expect(lowered).toEqual(hand);
99
+ });
100
+
101
+ it("omits `orderable` (does not emit false) when col() left it out", () => {
102
+ const lowered = routeToResourceSpec(vendorRoute());
103
+ expect("orderable" in lowered.columns[1]).toBe(false);
104
+ });
105
+
106
+ it("throws on a cards body (unwired view)", () => {
107
+ const route = defineRoute({
108
+ ...vendorRoute(),
109
+ view: { kind: "cards", item: {} },
110
+ });
111
+ expect(() => routeToResourceSpec(route)).toThrow(/not wired/);
112
+ });
113
+
114
+ it("throws on a multi-view route", () => {
115
+ const route = defineRoute({
116
+ ...vendorRoute(),
117
+ view: { views: { t: table({ columns: [] }) }, default: "t" },
118
+ });
119
+ expect(() => routeToResourceSpec(route)).toThrow(/multi-view/);
120
+ });
121
+
122
+ it("throws on an unwired field kind", () => {
123
+ expect(() => fieldToUiField("due", { kind: "date", label: "Due" })).toThrow(/unwired kind/);
124
+ });
125
+ });
@@ -0,0 +1,158 @@
1
+ // Pure builder layer for declarative resource routes. defineRoute() composes a
2
+ // frozen RouteSpec out of small helpers; the adapter (./route-adapter) folds it
3
+ // DOWN to the existing ResourceUiSpec, so ResourcePage stays the one render
4
+ // engine. No solid-js — this module is data only, like ./spec.ts.
5
+ import type { ColumnTone, UiColumnRender, UiFilter, UiDetailRow } from "./spec.js";
6
+ import type { FormSpec } from "./form-spec.js";
7
+
8
+ // ---- columns ---------------------------------------------------------------
9
+
10
+ /** A column descriptor; `render` is the EXISTING UiColumnRender union, so
11
+ * no new cell-rendering path is introduced. */
12
+ export interface RouteColumn {
13
+ readonly key: string;
14
+ readonly title: string;
15
+ readonly orderable?: boolean;
16
+ readonly render: UiColumnRender;
17
+ }
18
+
19
+ // Cell.* helpers emit the existing UiColumnRender union — the title field is the
20
+ // column key, supplied at col() time, so these only carry the render shape.
21
+ export const Cell = {
22
+ Title: { type: "title" } as const satisfies UiColumnRender,
23
+ Enum: (labels: Readonly<Record<string, string>>): UiColumnRender => ({ type: "enum", labels }),
24
+ Status: (opts: {
25
+ active: { label: string; tone: ColumnTone };
26
+ inactive: { label: string; tone: ColumnTone };
27
+ }): UiColumnRender => ({ type: "status", active: opts.active, inactive: opts.inactive }),
28
+ Text: (opts?: { muted?: boolean }): UiColumnRender =>
29
+ opts?.muted === undefined ? { type: "text" } : { type: "text", muted: opts.muted },
30
+ } as const;
31
+
32
+ /** Build a column. `orderable` is omitted (not set false) when not requested so
33
+ * the lowered spec matches a hand-authored one that left it out. */
34
+ export function col(
35
+ key: string,
36
+ opts: { title: string; orderable?: boolean; render: UiColumnRender },
37
+ ): RouteColumn {
38
+ const c: RouteColumn =
39
+ opts.orderable === undefined
40
+ ? { key, title: opts.title, render: opts.render }
41
+ : { key, title: opts.title, orderable: opts.orderable, render: opts.render };
42
+ return Object.freeze(c);
43
+ }
44
+
45
+ // ---- body views ------------------------------------------------------------
46
+
47
+ /** A `table` body. `kind` keys a future body-renderer registry; today only
48
+ * `table` is wired (the adapter THROWS on anything else). `cards` is reserved
49
+ * in the union but the builder for it is intentionally absent so an unwired
50
+ * view is a tsc error at the call site, not a runtime hole. */
51
+ export interface TableView {
52
+ readonly kind: "table";
53
+ readonly columns: readonly RouteColumn[];
54
+ }
55
+ export interface CardsView {
56
+ readonly kind: "cards";
57
+ readonly item: unknown;
58
+ }
59
+ export type BodyView = TableView | CardsView;
60
+
61
+ /** The only wired body builder. */
62
+ export function table(opts: { columns: readonly RouteColumn[] }): TableView {
63
+ return Object.freeze({ kind: "table", columns: opts.columns });
64
+ }
65
+
66
+ // ---- header actions ---------------------------------------------------------
67
+
68
+ /** A header action. `flow` is a STRING id (never an inline fn) so a flow
69
+ * runtime can slot in behind a host-provided runner without recontracting. */
70
+ export interface RouteAction {
71
+ readonly id: string;
72
+ readonly label: string;
73
+ readonly icon?: string;
74
+ readonly variant?: string;
75
+ readonly flow: string;
76
+ }
77
+ export function action(
78
+ id: string,
79
+ opts: { label: string; icon?: string; variant?: string; flow: string },
80
+ ): RouteAction {
81
+ return Object.freeze({
82
+ id,
83
+ label: opts.label,
84
+ flow: opts.flow,
85
+ ...(opts.icon ? { icon: opts.icon } : {}),
86
+ ...(opts.variant ? { variant: opts.variant } : {}),
87
+ });
88
+ }
89
+
90
+ // ---- the route spec ---------------------------------------------------------
91
+
92
+ export interface RoutePermissions {
93
+ readonly view: string;
94
+ readonly edit: readonly string[];
95
+ readonly delete: string;
96
+ }
97
+
98
+ /** Labels carried straight onto the lowered ResourceUiSpec.labels. */
99
+ export type RouteLabels = {
100
+ readonly add: string;
101
+ readonly createTitle: string;
102
+ readonly createSubmit: string;
103
+ readonly editTitle: string;
104
+ readonly editSubmit: string;
105
+ readonly titleField: string;
106
+ readonly searchPlaceholder: string;
107
+ readonly empty: string;
108
+ readonly noResults: string;
109
+ readonly createErrorFallback: string;
110
+ readonly updateErrorFallback: string;
111
+ readonly networkError: string;
112
+ readonly archiveTitle: string;
113
+ readonly archiveMessage: string;
114
+ readonly archiveConfirm: string;
115
+ };
116
+
117
+ /** A route-load setting. Reserved for a host settings store; the adapter does
118
+ * not lower it (it is host-resolved later), so it is presentation metadata
119
+ * only for now. */
120
+ export interface SettingDecl {
121
+ readonly kind: "number";
122
+ readonly default: number;
123
+ }
124
+ export const setting = {
125
+ number: (opts: { default: number }): SettingDecl =>
126
+ Object.freeze({ kind: "number", default: opts.default }),
127
+ } as const;
128
+
129
+ export interface RouteSpec {
130
+ readonly path?: string;
131
+ readonly title: string;
132
+ readonly subtitle?: string;
133
+ readonly icon?: string;
134
+ readonly basePath: string;
135
+ readonly softDeleteField: string;
136
+ readonly testIdPrefix: string;
137
+ readonly permissions: RoutePermissions;
138
+ readonly header?: { readonly actions: readonly RouteAction[] };
139
+ readonly toolbar?: {
140
+ readonly search?: { readonly placeholder: string; readonly fields: readonly string[] };
141
+ readonly filters?: readonly UiFilter[];
142
+ };
143
+ readonly view:
144
+ | BodyView
145
+ | { readonly views: Readonly<Record<string, BodyView>>; readonly default: string };
146
+ readonly form?: FormSpec;
147
+ readonly detail: readonly UiDetailRow[];
148
+ readonly settings?: Readonly<Record<string, SettingDecl>>;
149
+ // Sticky-footer slot — reserved in the contract, not rendered yet (needs an
150
+ // additive page-shell `footer?` prop on the consumer side).
151
+ readonly footer?: unknown;
152
+ readonly labels: RouteLabels;
153
+ }
154
+
155
+ /** defineRoute composes a frozen RouteSpec. Pure — no rendering, no side effects. */
156
+ export function defineRoute(cfg: RouteSpec): RouteSpec {
157
+ return Object.freeze(cfg);
158
+ }
package/src/index.ts CHANGED
@@ -191,6 +191,14 @@ export type {
191
191
  } from "./components/composite/resource/ResourcePage";
192
192
  export * from "./components/composite/resource/spec";
193
193
 
194
+ // Builder layer over ResourceUiSpec: defineRoute/defineForm compose a frozen
195
+ // RouteSpec/FormSpec out of small helpers (col, Cell.*, table, action, field.*,
196
+ // setting.*), and routeToResourceSpec lowers it to the exact ResourceUiSpec a
197
+ // hand-authored spec would produce — ResourcePage stays the one render engine.
198
+ export * from "./components/composite/resource/route-spec";
199
+ export * from "./components/composite/resource/form-spec";
200
+ export { routeToResourceSpec } from "./components/composite/resource/route-adapter";
201
+
194
202
  // U6 — declarative file/media field for the spec-driven form runtime. Value is an
195
203
  // opaque asset handle; host injects onUpload + presignUrl (storage-agnostic).
196
204
  export { default as FileField, type FileFieldProps, type AssetHandle, type FileFieldStatus } from "./components/composite/FileField";
@@ -1,7 +1,10 @@
1
1
  // Shared Tailwind class string for text inputs across plugin forms: a
2
- // full-width rounded input with the dark zinc surface and the amber focus
2
+ // full-width rounded input with the themed input surface and the amber focus
3
3
  // ring. Copied verbatim from the packages remote UI atoms so the shared kit
4
4
  // owns the single source of truth. Pure string constant, no dependencies.
5
-
5
+ //
6
+ // bg reads --ks-input-bg (not --ks-border, THEME-SPEC §1.2's dedicated input
7
+ // surface token) — the border token happens to be a plausible dark literal so
8
+ // this silently looked right in the `dark` theme and only broke elsewhere.
6
9
  export const INPUT_CLASS =
7
- "w-full rounded-lg border border-zinc-700 bg-zinc-800/50 px-3 py-2 text-sm text-zinc-200 focus:border-amber-500/50 focus:outline-none";
10
+ "w-full rounded-lg border border-[var(--ks-input-border,#3f3f46)] bg-[var(--ks-input-bg,#18181b)] px-3 py-2 text-sm text-[var(--ks-fg,#ffffff)] focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)] focus:outline-none";
@@ -74,17 +74,39 @@ export function getAccountIcon(account: { icon?: string | null; type: string }):
74
74
  return DEFAULT_BY_TYPE[account.type] ?? Banknote;
75
75
  }
76
76
 
77
+ // bank has no exact-hue token (blue-400/500 family isn't in §1.2), so it
78
+ // drifts to the nearest semantic bucket (--ks-info, sky). e_wallet/capital's
79
+ // bg (amber-500) and border (amber-400) are each an EXACT hex match to
80
+ // --ks-warning and --ks-accent respectively, so those keep zero-regression
81
+ // color-mix rather than sharing one base. Same for cash: bg is an exact
82
+ // --ks-success match, border an exact --ks-success-fg match.
77
83
  const DEFAULT_TONE_BY_TYPE: Record<string, { text: string; bg: string; border: string }> = {
78
- bank: { text: "text-blue-400", bg: "bg-blue-500/10", border: "border-blue-400/40" },
79
- e_wallet: { text: "text-amber-400", bg: "bg-amber-500/10", border: "border-amber-400/40" },
80
- cash: { text: "text-emerald-400", bg: "bg-emerald-500/10", border: "border-emerald-400/40" },
81
- capital: { text: "text-amber-400", bg: "bg-amber-500/10", border: "border-amber-400/40" },
84
+ bank: {
85
+ text: "text-[var(--ks-info,#38bdf8)]",
86
+ bg: "bg-[color-mix(in_srgb,var(--ks-info,#38bdf8)_10%,transparent)]",
87
+ border: "border-[color-mix(in_srgb,var(--ks-info,#38bdf8)_40%,transparent)]",
88
+ },
89
+ e_wallet: {
90
+ text: "text-[var(--ks-accent,#fbbf24)]",
91
+ bg: "bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_10%,transparent)]",
92
+ border: "border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_40%,transparent)]",
93
+ },
94
+ cash: {
95
+ text: "text-[var(--ks-success-fg,#34d399)]",
96
+ bg: "bg-[color-mix(in_srgb,var(--ks-success,#10b981)_10%,transparent)]",
97
+ border: "border-[color-mix(in_srgb,var(--ks-success-fg,#34d399)_40%,transparent)]",
98
+ },
99
+ capital: {
100
+ text: "text-[var(--ks-accent,#fbbf24)]",
101
+ bg: "bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_10%,transparent)]",
102
+ border: "border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_40%,transparent)]",
103
+ },
82
104
  };
83
105
 
84
106
  const FALLBACK_TONE = {
85
- text: "text-zinc-300",
86
- bg: "bg-zinc-700/30",
87
- border: "border-zinc-700/60",
107
+ text: "text-[var(--ks-fg-muted,#a1a1aa)]",
108
+ bg: "bg-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_30%,transparent)]",
109
+ border: "border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_60%,transparent)]",
88
110
  };
89
111
 
90
112
  // Per-type accent tone for an account chip, or the account's own custom color.
@@ -9,16 +9,15 @@
9
9
  // still pass their own `markClass` to override.
10
10
 
11
11
  import { type JSX } from "solid-js";
12
+ import { injectCSS } from "./inject-css";
12
13
 
13
14
  const MARK_STYLE_ID = "ksui-mark-style";
14
15
 
15
16
  function ensureMarkStyle(): void {
16
- if (typeof document === "undefined") return;
17
- if (document.getElementById(MARK_STYLE_ID)) return;
18
- const style = document.createElement("style");
19
- style.id = MARK_STYLE_ID;
20
- style.textContent = `.ksui-mark{background-color:rgba(245,158,11,0.3);color:inherit;border-radius:2px;}`;
21
- document.head.appendChild(style);
17
+ injectCSS(
18
+ MARK_STYLE_ID,
19
+ `.ksui-mark{background-color:color-mix(in srgb, var(--ks-warning, #f59e0b) 30%, transparent);color:inherit;border-radius:2px;}`,
20
+ );
22
21
  }
23
22
 
24
23
  /** Case-insensitive substring test. Empty query matches everything. */
@@ -0,0 +1,13 @@
1
+ // Shared dedup so a component's CSS-in-JS string is appended to <head> once
2
+ // per id, no matter how many instances mount — replaces the copy-pasted
3
+ // createElement('style') pattern across ~14 components.
4
+ const injected = new Set<string>();
5
+
6
+ export function injectCSS(id: string, css: string): void {
7
+ if (injected.has(id) || typeof document === "undefined") return;
8
+ injected.add(id);
9
+ const style = document.createElement("style");
10
+ style.id = id;
11
+ style.textContent = css;
12
+ document.head.appendChild(style);
13
+ }
package/tailwind.js CHANGED
@@ -19,6 +19,10 @@
19
19
  /** @type {import('tailwindcss').Plugin} */
20
20
  export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
21
21
  // ─── Button intents ────────────────────────────────────────────────
22
+ // Amber/red/slate intent palette mirrors Button.tsx's own documented
23
+ // exception (component-authored tone system, not a 1:1 §1.2 surface/
24
+ // text/border mapping) — none of these rgba alpha values byte-match a
25
+ // §1.2 dark default, so left literal rather than guessed.
22
26
  // Primary (amber)
23
27
  addUtilities({
24
28
  ".ks-btn-primary": {
@@ -134,7 +138,7 @@ export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
134
138
  "align-items": "center",
135
139
  gap: "0.5rem",
136
140
  padding: "0.375rem 0.75rem",
137
- "background-color": "rgb(24 24 27)",
141
+ "background-color": "var(--ks-input-bg, rgb(24 24 27))",
138
142
  border: "1px solid rgb(63 63 70 / 0.6)",
139
143
  "border-radius": "0.25rem",
140
144
  color: "rgb(228 228 231)",
@@ -152,7 +156,7 @@ export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
152
156
  padding: "0.5rem 1rem",
153
157
  "text-align": "left",
154
158
  "font-weight": 500,
155
- color: "rgb(161 161 170)",
159
+ color: "var(--ks-fg-muted, rgb(161 161 170))",
156
160
  "font-size": "0.75rem",
157
161
  "text-transform": "uppercase",
158
162
  "letter-spacing": "0.05em",
@@ -168,7 +172,7 @@ export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
168
172
  ".ks-dt-empty": {
169
173
  padding: "2rem",
170
174
  "text-align": "center",
171
- color: "rgb(113 113 122)",
175
+ color: "var(--ks-fg-subtle, rgb(113 113 122))",
172
176
  },
173
177
  ".ks-dt-pager": {
174
178
  display: "flex",
@@ -177,7 +181,7 @@ export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
177
181
  padding: "0.75rem 1rem",
178
182
  "border-top": "1px solid rgb(39 39 42 / 0.6)",
179
183
  "font-size": "0.8125rem",
180
- color: "rgb(161 161 170)",
184
+ color: "var(--ks-fg-muted, rgb(161 161 170))",
181
185
  },
182
186
  });
183
187
 
@@ -194,7 +198,7 @@ export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
194
198
  padding: "1rem",
195
199
  },
196
200
  ".ks-modal-card": {
197
- "background-color": "rgb(24 24 27)",
201
+ "background-color": "var(--ks-overlay-surface, rgb(24 24 27))",
198
202
  border: "1px solid rgb(63 63 70 / 0.6)",
199
203
  "max-height": "calc(100vh - 2rem)",
200
204
  overflow: "auto",
@@ -246,7 +250,7 @@ export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
246
250
  "background-color": "rgba(59 130 246 / 0.1)",
247
251
  },
248
252
  ".ks-pill-neutral": {
249
- color: "rgb(161 161 170)",
253
+ color: "var(--ks-fg-muted, rgb(161 161 170))",
250
254
  "background-color": "rgb(63 63 70 / 0.3)",
251
255
  },
252
256
  });
@@ -259,7 +263,7 @@ export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
259
263
  gap: "0.25rem",
260
264
  padding: "0.25rem 0.5rem",
261
265
  "font-size": "0.75rem",
262
- color: "rgb(161 161 170)",
266
+ color: "var(--ks-fg-muted, rgb(161 161 170))",
263
267
  "background-color": "transparent",
264
268
  border: "1px solid rgb(63 63 70 / 0.6)",
265
269
  "border-radius": "0.25rem",