@kahitsan/ksui 0.30.0 → 0.31.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahitsan/ksui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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",
|
|
@@ -73,6 +73,10 @@ const styles: Record<string, string> = {
|
|
|
73
73
|
|
|
74
74
|
export type ButtonIntent = "primary" | "danger" | "secondary";
|
|
75
75
|
export type ButtonVariant = "clip1" | "clip2" | "ghost" | "link";
|
|
76
|
+
/** "md" (default) preserves the original px-4/py-2/text-sm footprint everywhere
|
|
77
|
+
* it's already used. "sm" is for dense contexts — e.g. a DataTable row action
|
|
78
|
+
* — where the default footprint reads as oversized against compact rows. */
|
|
79
|
+
export type ButtonSize = "sm" | "md";
|
|
76
80
|
|
|
77
81
|
export interface ButtonProps {
|
|
78
82
|
/** Element / component to render as (defaults to "button"). Intentionally
|
|
@@ -83,6 +87,7 @@ export interface ButtonProps {
|
|
|
83
87
|
as?: any;
|
|
84
88
|
intent?: ButtonIntent;
|
|
85
89
|
variant?: ButtonVariant;
|
|
90
|
+
size?: ButtonSize;
|
|
86
91
|
noRipple?: boolean;
|
|
87
92
|
noScanline?: boolean;
|
|
88
93
|
noGlow?: boolean;
|
|
@@ -164,17 +169,17 @@ interface VariantConfig {
|
|
|
164
169
|
const buttonVariantConfig: Record<ButtonVariant, VariantConfig> = {
|
|
165
170
|
clip1: {
|
|
166
171
|
effects: ["clip-top-left-bottom-right"],
|
|
167
|
-
baseClasses: "
|
|
172
|
+
baseClasses: "border",
|
|
168
173
|
overrideType: false,
|
|
169
174
|
},
|
|
170
175
|
clip2: {
|
|
171
176
|
effects: ["clip-top-right-bottom-left"],
|
|
172
|
-
baseClasses: "
|
|
177
|
+
baseClasses: "border",
|
|
173
178
|
overrideType: false,
|
|
174
179
|
},
|
|
175
180
|
ghost: {
|
|
176
181
|
effects: [],
|
|
177
|
-
baseClasses: "
|
|
182
|
+
baseClasses: "bg-transparent border-transparent hover:bg-current/10 hover:border-transparent",
|
|
178
183
|
overrideType: true,
|
|
179
184
|
},
|
|
180
185
|
link: {
|
|
@@ -185,6 +190,16 @@ const buttonVariantConfig: Record<ButtonVariant, VariantConfig> = {
|
|
|
185
190
|
},
|
|
186
191
|
};
|
|
187
192
|
|
|
193
|
+
// Padding/text/gap live here (not in variantConfig) so a variant's padding is
|
|
194
|
+
// never in the same class-string as size's — `cn()` is a plain join with no
|
|
195
|
+
// tailwind-merge dedup, so two conflicting padding utilities would silently
|
|
196
|
+
// race on Tailwind's generated CSS order instead of on usage order. `link` is
|
|
197
|
+
// always unpadded regardless of size (see buttonVariantConfig.link).
|
|
198
|
+
const buttonSizeConfig: Record<ButtonSize, { padding: string; text: string; gap: string }> = {
|
|
199
|
+
md: { padding: "px-4 py-2", text: "text-sm", gap: "gap-2" },
|
|
200
|
+
sm: { padding: "px-2.5 py-1", text: "text-xs", gap: "gap-1.5" },
|
|
201
|
+
};
|
|
202
|
+
|
|
188
203
|
interface Ripple {
|
|
189
204
|
id: number;
|
|
190
205
|
x: number;
|
|
@@ -201,6 +216,7 @@ const Button = (props: ButtonProps): JSX.Element => {
|
|
|
201
216
|
as: "button" as ButtonProps["as"],
|
|
202
217
|
intent: "primary" as ButtonIntent,
|
|
203
218
|
variant: "clip1" as ButtonVariant,
|
|
219
|
+
size: "md" as ButtonSize,
|
|
204
220
|
noRipple: false,
|
|
205
221
|
noScanline: false,
|
|
206
222
|
noGlow: false,
|
|
@@ -214,6 +230,7 @@ const Button = (props: ButtonProps): JSX.Element => {
|
|
|
214
230
|
"as",
|
|
215
231
|
"intent",
|
|
216
232
|
"variant",
|
|
233
|
+
"size",
|
|
217
234
|
"noRipple",
|
|
218
235
|
"noScanline",
|
|
219
236
|
"noGlow",
|
|
@@ -238,6 +255,7 @@ const Button = (props: ButtonProps): JSX.Element => {
|
|
|
238
255
|
const variantConfig = createMemo(
|
|
239
256
|
() => buttonVariantConfig[local.variant as ButtonVariant] || buttonVariantConfig.clip1,
|
|
240
257
|
);
|
|
258
|
+
const sizeConfig = createMemo(() => buttonSizeConfig[local.size as ButtonSize] || buttonSizeConfig.md);
|
|
241
259
|
|
|
242
260
|
const hasRipple = createMemo(() => {
|
|
243
261
|
if (local.noRipple) return false;
|
|
@@ -288,15 +306,22 @@ const Button = (props: ButtonProps): JSX.Element => {
|
|
|
288
306
|
|
|
289
307
|
const classes = createMemo(() => {
|
|
290
308
|
const coreClasses =
|
|
291
|
-
"select-none inline-flex items-center justify-center
|
|
309
|
+
"select-none inline-flex items-center justify-center font-medium transition-all duration-200 focus:outline-none rounded";
|
|
292
310
|
const textColor = intentConfig().textColor;
|
|
293
311
|
const backgroundClasses = variantConfig().overrideType
|
|
294
312
|
? ""
|
|
295
313
|
: `${intentConfig().background} ${intentConfig().hover}`;
|
|
296
314
|
const variantClasses = variantConfig().baseClasses;
|
|
315
|
+
// link is always unpadded (baseClasses carries its own "px-0 py-0") but
|
|
316
|
+
// still takes text/gap sizing; every other variant takes padding too.
|
|
317
|
+
const sizeClasses =
|
|
318
|
+
local.variant === "link"
|
|
319
|
+
? `${sizeConfig().text} ${sizeConfig().gap}`
|
|
320
|
+
: `${sizeConfig().padding} ${sizeConfig().text} ${sizeConfig().gap}`;
|
|
297
321
|
|
|
298
322
|
return cn(
|
|
299
323
|
coreClasses,
|
|
324
|
+
sizeClasses,
|
|
300
325
|
textColor,
|
|
301
326
|
backgroundClasses,
|
|
302
327
|
variantClasses,
|
|
@@ -66,7 +66,7 @@ const STYLE_ID = "ksui-datatable-style";
|
|
|
66
66
|
// Full list of overridable vars (default = host value):
|
|
67
67
|
// --ksui-dt-card-bg outer card background
|
|
68
68
|
// (host `.card-bg`: linear-gradient(135deg,#0f0f0f,#1a1a1a))
|
|
69
|
-
// --ksui-dt-radius outer card / control corner radius (0.
|
|
69
|
+
// --ksui-dt-radius outer card / control corner radius (0.625rem)
|
|
70
70
|
// --ksui-dt-border card / header / footer / control border (zinc-800/50, rgba(39,39,42,0.5))
|
|
71
71
|
// --ksui-dt-row-border row divider + expansion-row top border (zinc-800/30, rgba(39,39,42,0.3))
|
|
72
72
|
// --ksui-dt-control-bg filter button / menu / select / search / show-more bg (zinc-900, #18181b)
|
|
@@ -84,10 +84,21 @@ const STYLE_ID = "ksui-datatable-style";
|
|
|
84
84
|
// --ksui-dt-accent active accent text: active pager num / show-more hover (amber-400, #fbbf24)
|
|
85
85
|
// --ksui-dt-accent-bg active pager num bg (amber-600/20, rgba(217,119,6,0.2))
|
|
86
86
|
// --ksui-dt-accent-border focus/hover accent border: search focus, show-more hover (amber-500/40, rgba(245,158,11,0.4))
|
|
87
|
+
// --ksui-dt-good positive-status badge text, e.g. "Active" (emerald-400, #34d399)
|
|
88
|
+
// --ksui-dt-good-bg positive-status badge bg (emerald-400/14, rgba(52,211,153,0.14))
|
|
89
|
+
// --ksui-dt-danger negative-status badge text, e.g. "Overdue"/"Expired" (red-400, #f87171)
|
|
90
|
+
// --ksui-dt-danger-bg negative-status badge bg (red-400/14, rgba(248,113,113,0.14))
|
|
91
|
+
// --ksui-dt-thead-bg header-row tint, layered over card-bg to set it apart from data rows (white/3%, rgba(255,255,255,0.03))
|
|
92
|
+
//
|
|
93
|
+
// `.ksui-datatable-td-num` / `.ksui-datatable-th-num` (mono, tabular-nums,
|
|
94
|
+
// right-aligned, nowrap) and `.ksui-datatable-badge` (+ `-ok` / `-warn` /
|
|
95
|
+
// `-danger` modifiers, using the vars above) are opt-in utility classes a
|
|
96
|
+
// consumer passes via a column's `className` — DataTable itself never
|
|
97
|
+
// infers column semantics from data.
|
|
87
98
|
//
|
|
88
99
|
const DATATABLE_CSS = `
|
|
89
|
-
.ksui-datatable{background:var(--ksui-dt-card-bg,linear-gradient(135deg,#0f0f0f 0%,#1a1a1a 100%));border:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));border-radius:var(--ksui-dt-radius,0.
|
|
90
|
-
.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:
|
|
100
|
+
.ksui-datatable{background:var(--ksui-dt-card-bg,linear-gradient(135deg,#0f0f0f 0%,#1a1a1a 100%));border:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));border-radius:var(--ksui-dt-radius,0.625rem);color:var(--ksui-dt-fg,#e4e4e7);box-shadow:0 1px 2px rgba(0,0,0,0.3),0 8px 20px -10px rgba(0,0,0,0.5);}
|
|
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;}
|
|
91
102
|
.ksui-datatable-filters-inline{display:none;flex:1 1 0%;}
|
|
92
103
|
.ksui-datatable-filters-mobile{display:block;flex:1 1 0%;}
|
|
93
104
|
@media (min-width:768px){.ksui-datatable-filters-inline{display:block;}.ksui-datatable-filters-mobile{display:none;}}
|
|
@@ -105,9 +116,10 @@ const DATATABLE_CSS = `
|
|
|
105
116
|
.ksui-datatable-search-input:focus{border-color:var(--ksui-dt-accent-border,rgba(245,158,11,0.4));}
|
|
106
117
|
@media (min-width:640px){.ksui-datatable-search-input{width:18rem;}}
|
|
107
118
|
.ksui-datatable-scroll{overflow-x:auto;transition:opacity 0.15s ease;}
|
|
108
|
-
.ksui-datatable-table{width:100%;text-align:left;font-size:0.
|
|
109
|
-
.ksui-datatable-thead{border-bottom:1px solid var(--ksui-dt-border,rgba(39,39,42,0.5));font-size:0.
|
|
110
|
-
.ksui-datatable-th{padding:0.
|
|
119
|
+
.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,rgba(255,255,255,0.03));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,#71717a);}
|
|
121
|
+
.ksui-datatable-th{padding:0.5625rem 0.875rem;background:var(--ksui-dt-thead-bg,rgba(255,255,255,0.03));white-space:nowrap;}
|
|
122
|
+
.ksui-datatable-th-num{text-align:right;}
|
|
111
123
|
.ksui-datatable-th-sortable{cursor:pointer;transition:color 0.15s ease;}
|
|
112
124
|
.ksui-datatable-th-sortable:hover{color:var(--ksui-dt-text-strong,#d4d4d8);}
|
|
113
125
|
.ksui-datatable-th-inner{display:inline-flex;align-items:center;}
|
|
@@ -116,13 +128,19 @@ const DATATABLE_CSS = `
|
|
|
116
128
|
.ksui-datatable-row:hover{background-color:var(--ksui-dt-row-hover,rgba(39,39,42,0.5));}
|
|
117
129
|
.ksui-datatable-row-clickable{cursor:pointer;}
|
|
118
130
|
.ksui-datatable-row-clickable:active{background-color:var(--ksui-dt-row-active,rgba(39,39,42,0.7));}
|
|
119
|
-
.ksui-datatable-td{padding:0.
|
|
131
|
+
.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,#d4d4d8);}
|
|
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,#e4e4e7);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:34ch;}
|
|
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,rgba(52,211,153,0.14));color:var(--ksui-dt-good,#34d399);}
|
|
136
|
+
.ksui-datatable-badge-warn{background:var(--ksui-dt-accent-bg,rgba(217,119,6,0.2));color:var(--ksui-dt-accent,#fbbf24);}
|
|
137
|
+
.ksui-datatable-badge-danger{background:var(--ksui-dt-danger-bg,rgba(248,113,113,0.14));color:var(--ksui-dt-danger,#f87171);}
|
|
120
138
|
.ksui-datatable-expansion-row{border-top:1px solid var(--ksui-dt-row-border,rgba(39,39,42,0.3));background-color:var(--ksui-dt-expansion-bg,rgba(9,9,11,0.4));}
|
|
121
139
|
.ksui-datatable-expansion-td{padding:0;}
|
|
122
|
-
.ksui-datatable-skeleton{height:
|
|
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;}
|
|
123
141
|
@keyframes ksuiDatatablePulse{0%,100%{opacity:1;}50%{opacity:0.5;}}
|
|
124
142
|
.ksui-datatable-empty{padding:3rem 1rem;text-align:center;color:var(--ksui-dt-muted,#71717a);}
|
|
125
|
-
.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:
|
|
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;}
|
|
126
144
|
.ksui-datatable-info{font-size:0.75rem;color:var(--ksui-dt-muted,#71717a);}
|
|
127
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,#d4d4d8);transition:border-color 0.15s ease,color 0.15s ease;cursor:pointer;}
|
|
128
146
|
.ksui-datatable-showmore:hover:not(:disabled){border-color:var(--ksui-dt-accent-border,rgba(245,158,11,0.4));color:var(--ksui-dt-accent,#fbbf24);}
|
|
@@ -1,89 +1,74 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type JSX } from "solid-js";
|
|
2
2
|
|
|
3
3
|
export type StatusTone = "success" | "neutral" | "warning" | "danger" | "info";
|
|
4
4
|
|
|
5
5
|
interface ToneClass {
|
|
6
6
|
text: string;
|
|
7
|
-
/** Lighter background (default
|
|
7
|
+
/** Lighter background (default). */
|
|
8
8
|
bg: string;
|
|
9
|
-
/** Heavier background
|
|
9
|
+
/** Heavier background, used when `solid` is set. */
|
|
10
10
|
bgSolid: string;
|
|
11
|
-
border: string;
|
|
12
|
-
/** Fill color for the optional leading dot. */
|
|
13
|
-
dot: string;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
// Module-private tone palette. The caller maps its own domain enum
|
|
17
14
|
// (status / is_active / voucher state) to one of these tones and passes a
|
|
18
15
|
// plain label; nothing domain-specific leaks into this atom.
|
|
16
|
+
//
|
|
17
|
+
// Flat tinted bg + text, no border — a borderless chip reads cleaner at the
|
|
18
|
+
// compact sizes tables now use than the previous bordered pill did.
|
|
19
19
|
const TONE_CLASS: Record<StatusTone, ToneClass> = {
|
|
20
20
|
success: {
|
|
21
21
|
text: "text-emerald-400",
|
|
22
22
|
bg: "bg-emerald-500/10",
|
|
23
23
|
bgSolid: "bg-emerald-500/20",
|
|
24
|
-
border: "border-emerald-400/40",
|
|
25
|
-
dot: "bg-emerald-400",
|
|
26
24
|
},
|
|
27
25
|
neutral: {
|
|
28
26
|
text: "text-zinc-400",
|
|
29
27
|
bg: "bg-zinc-800/60",
|
|
30
28
|
bgSolid: "bg-zinc-700/40",
|
|
31
|
-
border: "border-zinc-600",
|
|
32
|
-
dot: "bg-zinc-400",
|
|
33
29
|
},
|
|
34
30
|
warning: {
|
|
35
31
|
text: "text-amber-400",
|
|
36
32
|
bg: "bg-amber-500/10",
|
|
37
33
|
bgSolid: "bg-amber-500/20",
|
|
38
|
-
border: "border-amber-400/40",
|
|
39
|
-
dot: "bg-amber-400",
|
|
40
34
|
},
|
|
41
35
|
danger: {
|
|
42
36
|
text: "text-red-400",
|
|
43
37
|
bg: "bg-red-500/10",
|
|
44
38
|
bgSolid: "bg-red-500/20",
|
|
45
|
-
border: "border-red-400/40",
|
|
46
|
-
dot: "bg-red-400",
|
|
47
39
|
},
|
|
48
40
|
info: {
|
|
49
41
|
text: "text-blue-400",
|
|
50
42
|
bg: "bg-blue-500/10",
|
|
51
43
|
bgSolid: "bg-blue-500/20",
|
|
52
|
-
border: "border-blue-400/40",
|
|
53
|
-
dot: "bg-blue-400",
|
|
54
44
|
},
|
|
55
45
|
};
|
|
56
46
|
|
|
57
47
|
interface StatusPillProps {
|
|
58
|
-
/** Text shown inside the pill (the caller's own label)
|
|
48
|
+
/** Text shown inside the pill (the caller's own label), rendered as-is —
|
|
49
|
+
* the caller decides case. */
|
|
59
50
|
label: string;
|
|
60
51
|
/** Domain-free tone selector. The caller maps its enum to one of these. */
|
|
61
52
|
tone: StatusTone;
|
|
62
|
-
/**
|
|
63
|
-
dot?: boolean;
|
|
64
|
-
/** Heavier /20 background (clients/vouchers style); default is the
|
|
65
|
-
* lighter /10 background (packages style). */
|
|
53
|
+
/** Heavier /20 background; default is the lighter /10 background. */
|
|
66
54
|
solid?: boolean;
|
|
67
55
|
/** Extra classes on the pill wrapper. */
|
|
68
56
|
class?: string;
|
|
69
57
|
testId?: string;
|
|
70
58
|
}
|
|
71
59
|
|
|
72
|
-
// Single status-pill atom
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
60
|
+
// Single status-pill atom: a flat tinted-bg text chip, no border, no leading
|
|
61
|
+
// dot, no forced case — the caller's label renders exactly as given. `solid`
|
|
62
|
+
// switches to the heavier background. Domain enum -> tone + label mapping
|
|
63
|
+
// stays with the caller.
|
|
76
64
|
export default function StatusPill(props: StatusPillProps): JSX.Element {
|
|
77
65
|
const tc = () => TONE_CLASS[props.tone];
|
|
78
66
|
const bg = () => (props.solid ? tc().bgSolid : tc().bg);
|
|
79
67
|
return (
|
|
80
68
|
<span
|
|
81
69
|
data-testid={props.testId}
|
|
82
|
-
class={`inline-flex items-center
|
|
70
|
+
class={`inline-flex items-center text-xs px-1.5 py-0.5 rounded ${tc().text} ${bg()} ${props.class ?? ""}`}
|
|
83
71
|
>
|
|
84
|
-
<Show when={props.dot}>
|
|
85
|
-
<span class={`h-1.5 w-1.5 rounded-full ${tc().dot}`} aria-hidden="true" />
|
|
86
|
-
</Show>
|
|
87
72
|
{props.label}
|
|
88
73
|
</span>
|
|
89
74
|
);
|
|
@@ -5,6 +5,14 @@ interface TooltipProps {
|
|
|
5
5
|
content: JSX.Element;
|
|
6
6
|
/** Where the bubble sits relative to the trigger. Default "top". */
|
|
7
7
|
placement?: "top" | "bottom";
|
|
8
|
+
/** Horizontal anchor. "center" (default) centers the bubble on the trigger;
|
|
9
|
+
* "start" left-aligns it to the trigger's left edge instead — use when the
|
|
10
|
+
* trigger sits near a left edge (sidebar, viewport) where a centered bubble
|
|
11
|
+
* would slide underneath it. */
|
|
12
|
+
align?: "center" | "start";
|
|
13
|
+
/** Wrap long copy inside a fixed-width bubble instead of one nowrap line —
|
|
14
|
+
* pairs with `align="start"` for descriptive copy near a screen edge. */
|
|
15
|
+
wrap?: boolean;
|
|
8
16
|
/** Allow the trigger to be any element; the wrapper is a span. */
|
|
9
17
|
children: JSX.Element;
|
|
10
18
|
/** Extra classes on the wrapper. The wrapper is inline-flex so it sits
|
|
@@ -43,6 +51,7 @@ interface TooltipProps {
|
|
|
43
51
|
export default function Tooltip(props: TooltipProps): JSX.Element {
|
|
44
52
|
const tooltipId = props.id ?? createUniqueId();
|
|
45
53
|
const placement = () => props.placement ?? "top";
|
|
54
|
+
const align = () => props.align ?? "center";
|
|
46
55
|
return (
|
|
47
56
|
<span
|
|
48
57
|
class={`relative inline-flex group/tt ${props.class ?? ""}`}
|
|
@@ -53,7 +62,9 @@ export default function Tooltip(props: TooltipProps): JSX.Element {
|
|
|
53
62
|
<span
|
|
54
63
|
role="tooltip"
|
|
55
64
|
id={tooltipId}
|
|
56
|
-
class={`pointer-events-none absolute
|
|
65
|
+
class={`pointer-events-none absolute px-2 py-1 rounded-md bg-zinc-900 border border-zinc-700/80 text-[11px] text-zinc-100 opacity-0 group-hover/tt:opacity-100 group-focus-within/tt:opacity-100 transition-opacity duration-150 delay-150 z-50 shadow-lg ${
|
|
66
|
+
align() === "start" ? "left-0" : "left-1/2 -translate-x-1/2"
|
|
67
|
+
} ${props.wrap ? "whitespace-normal w-max max-w-[260px]" : "whitespace-nowrap"} ${
|
|
57
68
|
placement() === "bottom" ? "top-full mt-1" : "bottom-full mb-1"
|
|
58
69
|
}`}
|
|
59
70
|
>
|
|
@@ -31,7 +31,7 @@ export function renderCell(
|
|
|
31
31
|
case "status": {
|
|
32
32
|
const active = Boolean(raw);
|
|
33
33
|
const arm = active ? r.active : r.inactive;
|
|
34
|
-
return <StatusPill label={arm.label} tone={arm.tone}
|
|
34
|
+
return <StatusPill label={arm.label} tone={arm.tone} solid />;
|
|
35
35
|
}
|
|
36
36
|
case "text":
|
|
37
37
|
default: {
|