@svadmin/ui 0.5.7 → 0.5.10

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": "@svadmin/ui",
3
- "version": "0.5.7",
3
+ "version": "0.5.10",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
package/src/app.css CHANGED
@@ -1,5 +1,4 @@
1
- /* Typography — Inter font via Bunny Fonts (global CDN, works in China) */
2
- @import url('https://fonts.bunny.net/css?family=inter:400,500,600,700&display=swap');
1
+ /* Typography — Inter font should be imported in index.html for New York style */
3
2
 
4
3
  /* @svadmin/ui — Design Tokens (Shadcn New York style)
5
4
  * Auto-imported by AdminApp. Can be overridden by consumer's CSS.
@@ -94,6 +93,7 @@
94
93
  [data-slot="button"],
95
94
  [data-slot="input"],
96
95
  [data-slot="select-trigger"],
96
+ [data-slot="select"],
97
97
  [data-slot="textarea"] {
98
98
  @apply shadow-sm;
99
99
  }
@@ -101,6 +101,11 @@
101
101
  [data-slot="card"] {
102
102
  @apply shadow-sm;
103
103
  }
104
+
105
+ [data-slot="dialog-content"],
106
+ [data-slot="sheet-content"] {
107
+ @apply shadow-lg;
108
+ }
104
109
  }
105
110
 
106
111
  body {
@@ -154,8 +159,8 @@ body {
154
159
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
155
160
  --color-sidebar-border: var(--sidebar-border);
156
161
  --color-sidebar-ring: var(--sidebar-ring);
157
- --radius-sm: calc(var(--radius) - 6px);
158
- --radius-md: calc(var(--radius) - 4px);
159
- --radius-lg: calc(var(--radius) - 2px);
160
- --radius-xl: var(--radius);
162
+ --radius-sm: calc(var(--radius) - 4px);
163
+ --radius-md: calc(var(--radius) - 2px);
164
+ --radius-lg: var(--radius);
165
+ --radius-xl: calc(var(--radius) + 4px);
161
166
  }
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
2
3
  import { useSelect } from '@svadmin/core';
3
4
  import type { Filter } from '@svadmin/core';
4
5
  import { Button } from './ui/button/index.js';
@@ -57,7 +58,8 @@
57
58
  onchange?.(null);
58
59
  }
59
60
 
60
- function handleSearchInput(v: string) {
61
+ function handleSearchInput(e: Event) {
62
+ const v = (e.currentTarget as HTMLInputElement).value;
61
63
  searchInputValue = v;
62
64
  onSearchChange(v);
63
65
  }
@@ -88,8 +90,8 @@
88
90
  <div class="flex items-center border-b px-2">
89
91
  <Search class="mr-1.5 h-3.5 w-3.5 shrink-0 opacity-50" />
90
92
  <Command.Input
91
- value={searchInputValue}
92
- onValueChange={handleSearchInput}
93
+ bind:value={searchInputValue}
94
+ oninput={handleSearchInput}
93
95
  placeholder="Search..."
94
96
  class="flex h-9 w-full bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground"
95
97
  />
@@ -3,10 +3,10 @@
3
3
  import { Button } from './ui/button/index.js';
4
4
  import { t } from '@svadmin/core/i18n';
5
5
 
6
- let { open = false, title, message = '',
6
+ let { open = $bindable(false), title, message = '',
7
7
  confirmText, cancelText, variant = 'danger',
8
8
  onconfirm, oncancel } = $props<{
9
- open: boolean;
9
+ open?: boolean;
10
10
  title?: string;
11
11
  message?: string;
12
12
  confirmText?: string;
@@ -67,5 +67,6 @@
67
67
  <ConfirmDialog
68
68
  bind:open={confirmOpen}
69
69
  title={t('common.deleteConfirm')}
70
- onConfirm={handleDelete}
70
+ onconfirm={handleDelete}
71
+ oncancel={() => confirmOpen = false}
71
72
  />
@@ -220,7 +220,7 @@
220
220
  {#if multiVal.length > 0}
221
221
  <div class="flex flex-wrap gap-1 mt-1">
222
222
  {#each multiVal as v}
223
- {@const label = field.options?.find(o => o.value === v)?.label ?? String(v)}
223
+ {@const label = field.options?.find((o: any) => o.value === v)?.label ?? String(v)}
224
224
  <Badge variant="secondary" class="gap-1">
225
225
  {label}
226
226
  <button type="button" onclick={() => toggleMulti(v)} class="ml-0.5 rounded-sm hover:text-destructive hover:bg-destructive/10 transition-colors" aria-label={t('common.clear')}>×</button>
@@ -63,7 +63,7 @@
63
63
  {placeholder}
64
64
  bind:value
65
65
  class="pl-9 pr-9"
66
- {autocomplete}
66
+ autocomplete={autocomplete as any}
67
67
  />
68
68
  <TooltipButton
69
69
  tooltip={showPassword ? 'Hide password' : 'Show password'}
@@ -244,7 +244,7 @@
244
244
  </Button>
245
245
  {/snippet}
246
246
  </DropdownMenu.Trigger>
247
- <DropdownMenu.Content side="right" align="end" class="w-40">
247
+ <DropdownMenu.Content align="end" class="w-40">
248
248
  {#each colorThemes as ct}
249
249
  <DropdownMenu.Item onclick={() => setColorTheme(ct.id)} class="gap-2">
250
250
  <span
@@ -2,7 +2,7 @@
2
2
  import { fly } from 'svelte/transition';
3
3
  import { useStepsForm } from '@svadmin/core';
4
4
  import type { FieldDefinition } from '@svadmin/core';
5
- import { getResource } from '@svadmin/core';
5
+ import { getResource, useOne } from '@svadmin/core';
6
6
  import { navigate } from '@svadmin/core/router';
7
7
  import { t } from '@svadmin/core/i18n';
8
8
  import { Button } from './ui/button/index.js';
@@ -49,13 +49,88 @@
49
49
  .filter((f): f is FieldDefinition => f != null && f.key !== primaryKey) ?? []
50
50
  );
51
51
 
52
+ const flatStepsFields = steps.flatMap(s => s.fields)
53
+ .map(key => resource.fields.find(f => f.key === key))
54
+ .filter((f): f is FieldDefinition => f != null);
55
+
56
+ const existingQuery = mode === 'edit' && id != null
57
+ ? stepsForm.query as { data?: { data: Record<string, unknown> }, isLoading: boolean }
58
+ : null;
59
+
60
+ let formData = $state<Record<string, unknown>>({});
61
+ let fieldErrors = $state<Record<string, string>>({});
62
+ let initialized = $state(false);
63
+
64
+ function getDefaultForType(field: FieldDefinition): unknown {
65
+ switch (field.type) {
66
+ case 'text': case 'textarea': case 'richtext': case 'image': return '';
67
+ case 'number': return 0;
68
+ case 'boolean': return false;
69
+ case 'tags': case 'images': case 'multiselect': return [];
70
+ case 'select': return field.options?.[0]?.value ?? '';
71
+ case 'json': return {};
72
+ default: return '';
73
+ }
74
+ }
75
+
76
+ $effect.pre(() => {
77
+ if (initialized) return;
78
+ if (mode === 'create') {
79
+ const defaults: Record<string, unknown> = {};
80
+ for (const field of flatStepsFields) {
81
+ defaults[field.key] = field.defaultValue ?? getDefaultForType(field);
82
+ }
83
+ formData = defaults;
84
+ initialized = true;
85
+ } else if (existingQuery && existingQuery.data?.data) {
86
+ formData = { ...existingQuery.data.data };
87
+ initialized = true;
88
+ }
89
+ });
90
+
91
+ function handleFieldChange(key: string, val: unknown) {
92
+ formData[key] = val;
93
+ if (fieldErrors[key]) delete fieldErrors[key];
94
+ }
95
+
96
+ function validateCurrentStep(): boolean {
97
+ const errors: Record<string, string> = {};
98
+ let valid = true;
99
+ for (const field of currentFields) {
100
+ const value = formData[field.key];
101
+ if (field.required && (value === undefined || value === null || value === '')) {
102
+ errors[field.key] = t('validation.required');
103
+ valid = false;
104
+ } else if (field.validate) {
105
+ const msg = field.validate(value);
106
+ if (msg) {
107
+ errors[field.key] = msg;
108
+ valid = false;
109
+ }
110
+ }
111
+ }
112
+ fieldErrors = errors;
113
+ return valid;
114
+ }
115
+
52
116
  function handleSubmit() {
117
+ if (!validateCurrentStep()) return;
118
+
53
119
  if (!isLastStep) {
54
120
  next();
55
121
  return;
56
122
  }
57
- stepsForm.onFinish(stepsForm.formValues);
123
+
124
+ const cleanData: Record<string, unknown> = {};
125
+ for (const field of flatStepsFields) {
126
+ const value = formData[field.key];
127
+ if (value !== undefined) cleanData[field.key] = value;
128
+ }
129
+
130
+ stepsForm.onFinish(cleanData, { redirect: 'list' });
58
131
  }
132
+
133
+ const isLoadingData = $derived(mode === 'edit' && !initialized);
59
134
  </script>
60
135
 
61
136
  <div class="space-y-6">
@@ -103,12 +178,15 @@
103
178
  <Card.CardContent>
104
179
  {#key currentStep}
105
180
  <form class="space-y-5" onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} in:fly={{ x: 30, duration: 250 }}>
106
- {#each currentFields as field}
181
+ {#each currentFields as field (field.key)}
107
182
  <FieldRenderer
108
183
  {field}
109
- value={stepsForm.formValues[field.key]}
110
- onchange={(v) => stepsForm.setFieldValue(field.key, v)}
184
+ value={formData[field.key]}
185
+ onchange={(v) => handleFieldChange(field.key, v)}
111
186
  />
187
+ {#if fieldErrors[field.key]}
188
+ <p class="text-destructive text-[0.8125rem] mt-1">{fieldErrors[field.key]}</p>
189
+ {/if}
112
190
  {/each}
113
191
 
114
192
  <div class="flex items-center justify-between pt-4">
@@ -5,12 +5,12 @@
5
5
  import type { Component } from 'svelte';
6
6
 
7
7
  export interface FieldComponentMap {
8
- [fieldType: string]: Component<Record<string, unknown>>;
8
+ [fieldType: string]: Component<any>;
9
9
  }
10
10
 
11
11
  // Default display component map (read-only field display)
12
12
  // Dynamically imports components only when first used
13
- const displayCache = new Map<string, Component<Record<string, unknown>>>();
13
+ const displayCache = new Map<string, Component<any>>();
14
14
 
15
15
  // Pre-import all field display components (tree-shaken by bundler)
16
16
  import BooleanField from './fields/BooleanField.svelte';
@@ -52,13 +52,13 @@ export const builtinDisplayComponents: FieldComponentMap = {
52
52
  // ─── Custom component registry ─────────────────────────────────
53
53
  // Users can register custom field display components at runtime
54
54
 
55
- const customDisplayComponents = new Map<string, Component<Record<string, unknown>>>();
55
+ const customDisplayComponents = new Map<string, Component<any>>();
56
56
 
57
57
  /**
58
58
  * Register a custom field display component for a given field type.
59
59
  * The component will be used by ShowPage and any other display contexts.
60
60
  */
61
- export function registerDisplayComponent(fieldType: string, component: Component<Record<string, unknown>>): void {
61
+ export function registerDisplayComponent(fieldType: string, component: Component<any>): void {
62
62
  customDisplayComponents.set(fieldType, component);
63
63
  }
64
64
 
@@ -67,7 +67,7 @@ export function registerDisplayComponent(fieldType: string, component: Component
67
67
  * Custom components take precedence over built-in ones.
68
68
  * Returns undefined if no component is registered for the type.
69
69
  */
70
- export function getDisplayComponent(fieldType: string): Component<Record<string, unknown>> | undefined {
70
+ export function getDisplayComponent(fieldType: string): Component<any> | undefined {
71
71
  return customDisplayComponents.get(fieldType) ?? builtinDisplayComponents[fieldType];
72
72
  }
73
73
 
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { Badge } from '../ui/badge/index.js';
3
3
 
4
- let { value } = $props<{
4
+ let { value, options } = $props<{
5
5
  value: (string | number)[] | null | undefined;
6
6
  options?: { label: string; value: string | number }[];
7
7
  }>();
@@ -11,8 +11,9 @@
11
11
 
12
12
  {#if items.length > 0}
13
13
  <div class="flex flex-wrap gap-1">
14
- {#each items as item}
15
- <Badge variant="secondary">{String(item)}</Badge>
14
+ {#each items as v}
15
+ {@const label = options?.find((o: { label: string; value: string | number }) => o.value === v)?.label ?? String(v)}
16
+ <Badge variant="secondary" class="font-normal">{label}</Badge>
16
17
  {/each}
17
18
  </div>
18
19
  {:else}
@@ -7,7 +7,7 @@
7
7
  }>();
8
8
 
9
9
  const label = $derived(
10
- options?.find(o => o.value === value)?.label ?? (value != null ? String(value) : '—')
10
+ options?.find((o: any) => o.value === value)?.label ?? (value != null ? String(value) : '—')
11
11
  );
12
12
  </script>
13
13
 
@@ -2,12 +2,14 @@
2
2
  import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
3
3
  import type { WithoutChildrenOrChild } from "bits-ui";
4
4
  import { cn } from "../../../utils.js";
5
+ import type { Snippet } from "svelte";
5
6
 
6
7
  let {
7
8
  ref = $bindable(null),
8
9
  class: className,
10
+ children,
9
11
  ...restProps
10
- }: WithoutChildrenOrChild<AlertDialogPrimitive.ContentProps> & { class?: string } = $props();
12
+ }: WithoutChildrenOrChild<AlertDialogPrimitive.ContentProps> & { children?: Snippet; class?: string } = $props();
11
13
  </script>
12
14
 
13
15
  <AlertDialogPrimitive.Portal>
@@ -21,5 +23,7 @@
21
23
  className,
22
24
  )}
23
25
  {...restProps}
24
- />
26
+ >
27
+ {@render children?.()}
28
+ </AlertDialogPrimitive.Content>
25
29
  </AlertDialogPrimitive.Portal>
@@ -2,7 +2,7 @@
2
2
  import { type VariantProps, tv } from "tailwind-variants";
3
3
 
4
4
  export const badgeVariants = tv({
5
- base: "h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-[3px] [&>svg]:pointer-events-none",
5
+ base: "h-5 gap-1 rounded-[min(var(--radius-sm),10px)] border border-transparent px-2.5 py-0.5 text-xs font-semibold transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-[3px] [&>svg]:pointer-events-none",
6
6
  variants: {
7
7
  variant: {
8
8
  default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
@@ -15,14 +15,14 @@
15
15
  link: "text-primary underline-offset-4 hover:underline",
16
16
  },
17
17
  size: {
18
- default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
18
+ default: "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
19
19
  xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
20
- sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
21
- lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
22
- icon: "size-8",
20
+ sm: "h-8 gap-1 rounded-[min(var(--radius-md),12px)] px-3 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5",
21
+ lg: "h-10 gap-2 px-4 has-data-[icon=inline-end]:pr-3.5 has-data-[icon=inline-start]:pl-3.5",
22
+ icon: "size-9",
23
23
  "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
24
- "icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
25
- "icon-lg": "size-9",
24
+ "icon-sm": "size-8 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
25
+ "icon-lg": "size-10",
26
26
  },
27
27
  },
28
28
  defaultVariants: {
@@ -10,6 +10,6 @@
10
10
  }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
11
  </script>
12
12
 
13
- <div bind:this={ref} data-slot="card-title" class={cn("text-base leading-snug font-medium group-data-[size=sm]/card:text-sm", className)} {...restProps}>
13
+ <div bind:this={ref} data-slot="card-title" class={cn("leading-none font-semibold tracking-tight group-data-[size=sm]/card:text-sm", className)} {...restProps}>
14
14
  {@render children?.()}
15
15
  </div>
@@ -28,7 +28,7 @@
28
28
  bind:ref
29
29
  data-slot="dialog-content"
30
30
  class={cn(
31
- "bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
31
+ "bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-6 text-sm ring-1 duration-100 sm:max-w-lg fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
32
32
  className
33
33
  )}
34
34
  {...restProps}
@@ -18,7 +18,7 @@
18
18
  <div
19
19
  bind:this={ref}
20
20
  data-slot="dialog-footer"
21
- class={cn("bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
21
+ class={cn("bg-muted/50 -mx-6 -mb-6 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
22
22
  {...restProps}
23
23
  >
24
24
  {@render children?.()}
@@ -12,6 +12,6 @@
12
12
  <DialogPrimitive.Title
13
13
  bind:ref
14
14
  data-slot="dialog-title"
15
- class={cn("text-base leading-none font-medium", className)}
15
+ class={cn("text-lg leading-none font-semibold", className)}
16
16
  {...restProps}
17
17
  />
@@ -25,7 +25,7 @@
25
25
  bind:this={ref}
26
26
  data-slot={dataSlot}
27
27
  class={cn(
28
- "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
28
+ "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-9 rounded-lg border bg-transparent px-3 py-1.5 text-base transition-colors file:h-7 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
29
29
  className
30
30
  )}
31
31
  type="file"
@@ -38,7 +38,7 @@
38
38
  bind:this={ref}
39
39
  data-slot={dataSlot}
40
40
  class={cn(
41
- "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
41
+ "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-9 rounded-lg border bg-transparent px-3 py-1.5 text-base transition-colors file:h-7 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
42
42
  className
43
43
  )}
44
44
  {type}
@@ -20,7 +20,7 @@
20
20
  bind:this={ref}
21
21
  data-slot="select"
22
22
  class={cn(
23
- "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 w-full appearance-none rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors focus-visible:ring-3 aria-invalid:ring-3 md:text-sm placeholder:text-muted-foreground outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 bg-[length:16px] bg-[right_8px_center] bg-no-repeat",
23
+ "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-9 w-full appearance-none rounded-lg border bg-transparent px-3 py-1.5 text-base transition-colors focus-visible:ring-3 aria-invalid:ring-3 md:text-sm placeholder:text-muted-foreground outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 bg-[length:16px] bg-[right_8px_center] bg-no-repeat",
24
24
  "bg-[url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e\")]",
25
25
  className
26
26
  )}
@@ -10,6 +10,6 @@
10
10
  }: WithElementRef<HTMLThAttributes> = $props();
11
11
  </script>
12
12
 
13
- <th bind:this={ref} data-slot="table-head" class={cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", className)} {...restProps}>
13
+ <th bind:this={ref} data-slot="table-head" class={cn("text-foreground h-9 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", className)} {...restProps}>
14
14
  {@render children?.()}
15
15
  </th>
@@ -27,7 +27,7 @@
27
27
  {sideOffset}
28
28
  {side}
29
29
  class={cn(
30
- "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs has-data-[slot=kbd]:pr-1.5 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm bg-foreground text-background z-50 w-fit max-w-xs origin-(--bits-tooltip-content-transform-origin)",
30
+ "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs has-data-[slot=kbd]:pr-1.5 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm bg-primary text-primary-foreground z-50 w-fit max-w-xs origin-(--bits-tooltip-content-transform-origin)",
31
31
  className
32
32
  )}
33
33
  {...restProps}
@@ -37,7 +37,7 @@
37
37
  {#snippet child({ props })}
38
38
  <div
39
39
  class={cn(
40
- "size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground z-50",
40
+ "size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-primary fill-primary z-50",
41
41
  "data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%+2px)]",
42
42
  "data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%+1px)]",
43
43
  "data-[side=right]:translate-x-[calc(50%+2px)] data-[side=right]:translate-y-1/2",