@kahitsan/ksui 0.12.0 → 0.13.1

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/README.md CHANGED
@@ -38,6 +38,25 @@ voucher and payment account pickers, the attachment tiles) call a backend that
38
38
  answers certain requests, so they shine inside an app built the KahitSan way, but
39
39
  they degrade gracefully without one. Each component page says what it needs.
40
40
 
41
+ Some components (Button, DataTable, DatePicker, etc.) use Tailwind utility
42
+ classes internally. To ensure these classes are included in your CSS output,
43
+ add the ksui Tailwind plugin to your project:
44
+
45
+ ```js
46
+ // tailwind.config.js (v3)
47
+ module.exports = {
48
+ plugins: [require("@kahitsan/ksui/tailwind")],
49
+ }
50
+ ```
51
+
52
+ ```css
53
+ /* app.css (v4) */
54
+ @plugin "@kahitsan/ksui/tailwind";
55
+ ```
56
+
57
+ Without the plugin, Tailwind may purge the utility classes ksui components use,
58
+ causing missing backgrounds or borders.
59
+
41
60
  ## What is inside
42
61
 
43
62
  The package is organized into three kinds of exports:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kahitsan/ksui",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
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",
@@ -17,10 +17,18 @@
17
17
  "types": "./src/components/*.tsx",
18
18
  "development": "./src/components/*.tsx",
19
19
  "import": "./src/components/*.tsx"
20
- }
20
+ },
21
+ "./utils/*": {
22
+ "solid": "./src/utils/*.ts",
23
+ "types": "./src/utils/*.ts",
24
+ "development": "./src/utils/*.ts",
25
+ "import": "./src/utils/*.ts"
26
+ },
27
+ "./tailwind": "./tailwind.js"
21
28
  },
22
29
  "files": [
23
- "src"
30
+ "src",
31
+ "tailwind.js"
24
32
  ],
25
33
  "scripts": {
26
34
  "typecheck": "tsc --noEmit",
@@ -32,7 +40,13 @@
32
40
  "lucide-solid": "^1.7.0"
33
41
  },
34
42
  "peerDependencies": {
35
- "solid-js": "^1.9.0"
43
+ "solid-js": "^1.9.0",
44
+ "tailwindcss": ">=3.0.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "tailwindcss": {
48
+ "optional": true
49
+ }
36
50
  },
37
51
  "devDependencies": {
38
52
  "@changesets/cli": "^2.31.0",
@@ -46,9 +46,9 @@ function ensureModalStyle(): void {
46
46
  .ksui-modal-sheet-overlay{position:fixed;inset:0;z-index:50;display:flex;align-items:flex-end;justify-content:center;}
47
47
  @media (min-width:640px){.ksui-modal-sheet-overlay{align-items:center;padding:1rem;}}
48
48
  .ksui-modal-sheet-backdrop{position:absolute;inset:0;background:rgba(0,0,0,0.7);backdrop-filter:blur(6px);}
49
- .ksui-modal-card{position:relative;z-index:10;width:100%;background:var(--ksui-modal-bg,#11161f);color:var(--ksui-modal-fg,inherit);border:1px solid var(--ksui-modal-border,rgba(245,158,11,0.3));border-radius:0.75rem;padding:1.5rem;box-shadow:0 25px 50px -12px rgba(0,0,0,0.6);max-height:90vh;overflow-x:hidden;overflow-y:auto;}
49
+ .ksui-modal-card{position:relative;z-index:10;width:100%;background:var(--ksui-modal-bg,#18181b);color:var(--ksui-modal-fg,inherit);border:1px solid var(--ksui-modal-border,rgba(245,158,11,0.3));border-radius:0.75rem;padding:1.5rem;box-shadow:0 25px 50px -12px rgba(0,0,0,0.6);max-height:90vh;overflow-x:hidden;overflow-y:auto;}
50
50
  .ksui-modal-card.danger{border-color:var(--ksui-modal-border-danger,rgba(239,68,68,0.3));}
51
- .ksui-modal-sheet-card{position:relative;z-index:10;width:100%;background:var(--ksui-modal-bg,#11161f);color:var(--ksui-modal-fg,inherit);border:1px solid var(--ksui-modal-border,rgba(245,158,11,0.3));box-shadow:0 25px 50px -12px rgba(0,0,0,0.6);max-height:92vh;overflow:hidden;overscroll-behavior:contain;}
51
+ .ksui-modal-sheet-card{position:relative;z-index:10;width:100%;background:var(--ksui-modal-bg,#18181b);color:var(--ksui-modal-fg,inherit);border:1px solid var(--ksui-modal-border,rgba(245,158,11,0.3));box-shadow:0 25px 50px -12px rgba(0,0,0,0.6);max-height:92vh;overflow:hidden;overscroll-behavior:contain;}
52
52
  .ksui-modal-sheet-card.danger{border-color:var(--ksui-modal-border-danger,rgba(239,68,68,0.3));}
53
53
  @media (min-width:640px){.ksui-modal-sheet-card{width:auto;border-radius:0.75rem;max-height:88vh;}}
54
54
  `;
package/src/index.ts CHANGED
@@ -165,6 +165,14 @@ export { INPUT_CLASS } from "./utils/INPUT_CLASS";
165
165
  export { formatPHP } from "./utils/formatPHP";
166
166
  export { formatShortDate } from "./utils/formatShortDate";
167
167
  export { formatFullDate } from "./utils/formatFullDate";
168
+ export {
169
+ parseDateInput,
170
+ normalizeDate,
171
+ formatDateDisplay,
172
+ formatDateEditable,
173
+ formatTimeDisplay,
174
+ } from "./utils/parse-date";
175
+ export type { ParsedDate } from "./utils/parse-date";
168
176
 
169
177
  // Self-contained helpers promoted from the former host kit so the library has no
170
178
  // "@kserp/host-ui" dependency.
@@ -54,7 +54,7 @@ export function confirm(opts: ConfirmOptions): Promise<boolean> {
54
54
  tone={opts.danger ? "danger" : "default"}
55
55
  ariaLabel={opts.title ?? "Confirm"}
56
56
  >
57
- <div style={{ display: "flex", "flex-direction": "column", gap: "0.75rem" }}>
57
+ <div data-testid="confirm-dialog" style={{ display: "flex", "flex-direction": "column", gap: "0.75rem" }}>
58
58
  <Show when={opts.title}>
59
59
  <h2 style={{ margin: 0, "font-size": "1.125rem", "font-weight": 600 }}>{opts.title}</h2>
60
60
  </Show>
package/tailwind.js ADDED
@@ -0,0 +1,278 @@
1
+ /**
2
+ * @kahitsan/ksui Tailwind CSS plugin
3
+ *
4
+ * Registers all ksui component utility classes so consumers don't need
5
+ * manual safelisting. Load from CSS:
6
+ *
7
+ * // app.css (v4)
8
+ * @plugin "@kahitsan/ksui/tailwind";
9
+ *
10
+ * This plugin registers .ks-* component classes via addComponents/addUtilities.
11
+ * Raw Tailwind utilities used by ksui components (bg-amber-600/20, etc.)
12
+ * require a separate @source directive pointing at the ksui src directory.
13
+ *
14
+ * IMPORTANT: This file uses ESM (export default) because the ksui package.json
15
+ * sets "type": "module". CommonJS (module.exports) will fail to load under
16
+ * Tailwind v4's @plugin directive, which uses dynamic import().
17
+ */
18
+
19
+ /** @type {import('tailwindcss').Plugin} */
20
+ export default function ksuiTailwindPlugin({ addUtilities, addComponents }) {
21
+ // ─── Button intents ────────────────────────────────────────────────
22
+ // Primary (amber)
23
+ addUtilities({
24
+ ".ks-btn-primary": {
25
+ color: "rgb(251 191 36)", // text-amber-400
26
+ "background-color": "rgba(217 119 6 / 0.2)", // bg-amber-600/20
27
+ border: "1px solid rgba(217 119 6 / 0.6)", // border-amber-600/60
28
+ },
29
+ ".ks-btn-primary:hover": {
30
+ "background-color": "rgba(217 119 6 / 0.3)", // hover:bg-amber-600/30
31
+ "border-color": "rgb(245 158 11)", // hover:border-amber-500
32
+ },
33
+ });
34
+
35
+ // Danger (red)
36
+ addUtilities({
37
+ ".ks-btn-danger": {
38
+ color: "rgb(248 113 113)", // text-red-400
39
+ "background-color": "rgba(220 38 38 / 0.2)", // bg-red-600/20
40
+ border: "1px solid rgba(220 38 38 / 0.6)", // border-red-600/60
41
+ },
42
+ ".ks-btn-danger:hover": {
43
+ "background-color": "rgba(220 38 38 / 0.3)", // hover:bg-red-600/30
44
+ "border-color": "rgb(239 68 68)", // hover:border-red-500
45
+ },
46
+ });
47
+
48
+ // Secondary (slate)
49
+ addUtilities({
50
+ ".ks-btn-secondary": {
51
+ color: "rgb(148 163 184)", // text-slate-400
52
+ "background-color": "rgba(71 85 105 / 0.2)", // bg-slate-600/20
53
+ border: "1px solid rgba(71 85 105 / 0.6)", // border-slate-600/60
54
+ },
55
+ ".ks-btn-secondary:hover": {
56
+ "background-color": "rgba(71 85 105 / 0.3)", // hover:bg-slate-600/30
57
+ "border-color": "rgb(100 116 139)", // hover:border-slate-500
58
+ },
59
+ });
60
+
61
+ // Ghost
62
+ addUtilities({
63
+ ".ks-btn-ghost": {
64
+ "background-color": "transparent",
65
+ border: "1px solid transparent",
66
+ },
67
+ ".ks-btn-ghost:hover": {
68
+ "background-color": "rgb(255 255 255 / 0.1)", // hover:bg-current/10
69
+ },
70
+ });
71
+
72
+ // Link
73
+ addUtilities({
74
+ ".ks-btn-link": {
75
+ "background-color": "transparent",
76
+ border: "none",
77
+ "text-decoration": "underline",
78
+ "text-underline-offset": "4px",
79
+ },
80
+ ".ks-btn-link:hover": {
81
+ "background-color": "transparent",
82
+ },
83
+ });
84
+
85
+ // ─── Button base ───────────────────────────────────────────────────
86
+ addComponents({
87
+ ".ks-btn": {
88
+ display: "inline-flex",
89
+ "align-items": "center",
90
+ "justify-content": "center",
91
+ gap: "0.5rem",
92
+ "font-weight": 500,
93
+ "font-size": "0.875rem",
94
+ "line-height": "1.25rem",
95
+ "border-radius": "0.25rem",
96
+ padding: "0.5rem 1rem",
97
+ "user-select": "none",
98
+ transition: "all 0.15s ease",
99
+ outline: "none",
100
+ },
101
+ ".ks-btn:focus-visible": {
102
+ "outline": "2px solid rgb(217 119 6 / 0.5)",
103
+ "outline-offset": "2px",
104
+ },
105
+ ".ks-btn:disabled": {
106
+ opacity: 0.5,
107
+ cursor: "not-allowed",
108
+ },
109
+ ".ks-btn-sm": {
110
+ "font-size": "0.75rem",
111
+ padding: "0.375rem 0.75rem",
112
+ },
113
+ ".ks-btn-lg": {
114
+ "font-size": "1rem",
115
+ padding: "0.625rem 1.25rem",
116
+ },
117
+ });
118
+
119
+ // ─── DataTable ─────────────────────────────────────────────────────
120
+ addComponents({
121
+ ".ks-dt": {
122
+ "font-size": "0.875rem",
123
+ "line-height": "1.25rem",
124
+ },
125
+ ".ks-dt-header": {
126
+ display: "flex",
127
+ "align-items": "center",
128
+ gap: "0.5rem",
129
+ padding: "0.75rem 1rem",
130
+ "border-bottom": "1px solid rgb(39 39 42 / 0.6)",
131
+ },
132
+ ".ks-dt-search": {
133
+ display: "flex",
134
+ "align-items": "center",
135
+ gap: "0.5rem",
136
+ padding: "0.375rem 0.75rem",
137
+ "background-color": "rgb(24 24 27)",
138
+ border: "1px solid rgb(63 63 70 / 0.6)",
139
+ "border-radius": "0.25rem",
140
+ color: "rgb(228 228 231)",
141
+ "font-size": "0.875rem",
142
+ },
143
+ ".ks-dt-search:focus": {
144
+ outline: "none",
145
+ "border-color": "rgb(217 119 6 / 0.5)",
146
+ },
147
+ ".ks-dt-table": {
148
+ width: "100%",
149
+ "border-collapse": "collapse",
150
+ },
151
+ ".ks-dt-th": {
152
+ padding: "0.5rem 1rem",
153
+ "text-align": "left",
154
+ "font-weight": 500,
155
+ color: "rgb(161 161 170)",
156
+ "font-size": "0.75rem",
157
+ "text-transform": "uppercase",
158
+ "letter-spacing": "0.05em",
159
+ "border-bottom": "1px solid rgb(39 39 42 / 0.6)",
160
+ cursor: "pointer",
161
+ "user-select": "none",
162
+ },
163
+ ".ks-dt-td": {
164
+ padding: "0.625rem 1rem",
165
+ "border-bottom": "1px solid rgb(39 39 42 / 0.3)",
166
+ color: "rgb(228 228 231)",
167
+ },
168
+ ".ks-dt-empty": {
169
+ padding: "2rem",
170
+ "text-align": "center",
171
+ color: "rgb(113 113 122)",
172
+ },
173
+ ".ks-dt-pager": {
174
+ display: "flex",
175
+ "align-items": "center",
176
+ "justify-content": "space-between",
177
+ padding: "0.75rem 1rem",
178
+ "border-top": "1px solid rgb(39 39 42 / 0.6)",
179
+ "font-size": "0.8125rem",
180
+ color: "rgb(161 161 170)",
181
+ },
182
+ });
183
+
184
+ // ─── Modal ─────────────────────────────────────────────────────────
185
+ addComponents({
186
+ ".ks-modal-backdrop": {
187
+ position: "fixed",
188
+ inset: 0,
189
+ "background-color": "rgba(0, 0, 0, 0.6)",
190
+ "z-index": 50,
191
+ display: "flex",
192
+ "align-items": "center",
193
+ "justify-content": "center",
194
+ padding: "1rem",
195
+ },
196
+ ".ks-modal-card": {
197
+ "background-color": "rgb(24 24 27)",
198
+ border: "1px solid rgb(63 63 70 / 0.6)",
199
+ "max-height": "calc(100vh - 2rem)",
200
+ overflow: "auto",
201
+ position: "relative",
202
+ },
203
+ ".ks-modal-sm": { "max-width": "24rem" },
204
+ ".ks-modal-md": { "max-width": "32rem" },
205
+ ".ks-modal-lg": { "max-width": "40rem" },
206
+ ".ks-modal-xl": { "max-width": "50rem" },
207
+ });
208
+
209
+ // ─── FormErrorBanner ───────────────────────────────────────────────
210
+ addComponents({
211
+ ".ks-form-error": {
212
+ padding: "0.5rem 0.75rem",
213
+ "background-color": "rgba(239 68 68 / 0.1)",
214
+ border: "1px solid rgba(239 68 68 / 0.3)",
215
+ color: "rgb(248 113 113)",
216
+ "font-size": "0.75rem",
217
+ "border-radius": "0.375rem",
218
+ },
219
+ });
220
+
221
+ // ─── StatusPill ────────────────────────────────────────────────────
222
+ addComponents({
223
+ ".ks-pill": {
224
+ display: "inline-flex",
225
+ "align-items": "center",
226
+ gap: "0.25rem",
227
+ padding: "0.125rem 0.5rem",
228
+ "font-size": "0.75rem",
229
+ "font-weight": 500,
230
+ "border-radius": "9999px",
231
+ },
232
+ ".ks-pill-success": {
233
+ color: "rgb(74 222 128)",
234
+ "background-color": "rgba(74 222 128 / 0.1)",
235
+ },
236
+ ".ks-pill-danger": {
237
+ color: "rgb(248 113 113)",
238
+ "background-color": "rgba(239 68 68 / 0.1)",
239
+ },
240
+ ".ks-pill-warning": {
241
+ color: "rgb(251 191 36)",
242
+ "background-color": "rgba(245 158 11 / 0.1)",
243
+ },
244
+ ".ks-pill-info": {
245
+ color: "rgb(96 165 250)",
246
+ "background-color": "rgba(59 130 246 / 0.1)",
247
+ },
248
+ ".ks-pill-neutral": {
249
+ color: "rgb(161 161 170)",
250
+ "background-color": "rgb(63 63 70 / 0.3)",
251
+ },
252
+ });
253
+
254
+ // ─── CopyButton ────────────────────────────────────────────────────
255
+ addComponents({
256
+ ".ks-copy-btn": {
257
+ display: "inline-flex",
258
+ "align-items": "center",
259
+ gap: "0.25rem",
260
+ padding: "0.25rem 0.5rem",
261
+ "font-size": "0.75rem",
262
+ color: "rgb(161 161 170)",
263
+ "background-color": "transparent",
264
+ border: "1px solid rgb(63 63 70 / 0.6)",
265
+ "border-radius": "0.25rem",
266
+ cursor: "pointer",
267
+ transition: "all 0.15s ease",
268
+ },
269
+ ".ks-copy-btn:hover": {
270
+ color: "rgb(228 228 231)",
271
+ "border-color": "rgb(82 82 91)",
272
+ },
273
+ });
274
+ };
275
+
276
+ export const meta = {
277
+ name: "@kahitsan/ksui",
278
+ };