@classic-homes/theme-svelte 0.1.0 → 0.1.2

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.
@@ -81,7 +81,7 @@
81
81
 
82
82
  <AlertDialogPrimitive.Root bind:open onOpenChange={handleOpenChange}>
83
83
  {#if trigger}
84
- <AlertDialogPrimitive.Trigger asChild>
84
+ <AlertDialogPrimitive.Trigger>
85
85
  {#snippet child({ props })}
86
86
  <span {...props}>
87
87
  {@render trigger()}
@@ -1,4 +1,4 @@
1
- <script lang="ts" generics="T extends Record<string, unknown>">
1
+ <script lang="ts" generics="T extends object">
2
2
  /**
3
3
  * DataTable - A sortable, accessible data table component
4
4
  *
@@ -52,7 +52,7 @@
52
52
  */
53
53
  import type { DataTableColumn } from '../types/components.js';
54
54
  import type { Snippet } from 'svelte';
55
- declare function $$render<T extends Record<string, unknown>>(): {
55
+ declare function $$render<T extends object>(): {
56
56
  props: {
57
57
  /** Array of data rows */
58
58
  data: T[];
@@ -84,7 +84,7 @@ declare function $$render<T extends Record<string, unknown>>(): {
84
84
  slots: {};
85
85
  events: {};
86
86
  };
87
- declare class __sveltets_Render<T extends Record<string, unknown>> {
87
+ declare class __sveltets_Render<T extends object> {
88
88
  props(): ReturnType<typeof $$render<T>>['props'];
89
89
  events(): ReturnType<typeof $$render<T>>['events'];
90
90
  slots(): ReturnType<typeof $$render<T>>['slots'];
@@ -92,12 +92,12 @@ declare class __sveltets_Render<T extends Record<string, unknown>> {
92
92
  exports(): {};
93
93
  }
94
94
  interface $$IsomorphicComponent {
95
- new <T extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
95
+ new <T extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
96
96
  $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
97
97
  } & ReturnType<__sveltets_Render<T>['exports']>;
98
- <T extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
98
+ <T extends object>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
99
99
  z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
100
100
  }
101
101
  declare const DataTable: $$IsomorphicComponent;
102
- type DataTable<T extends Record<string, unknown>> = InstanceType<typeof DataTable<T>>;
102
+ type DataTable<T extends object> = InstanceType<typeof DataTable<T>>;
103
103
  export default DataTable;
@@ -20,6 +20,10 @@
20
20
  trigger?: Snippet;
21
21
  /** Optional footer element */
22
22
  footer?: Snippet;
23
+ /** Optional header actions (rendered before the close button) */
24
+ headerActions?: Snippet;
25
+ /** Whether to show the close button (default: true) */
26
+ showClose?: boolean;
23
27
  }
24
28
 
25
29
  let {
@@ -31,17 +35,24 @@
31
35
  children,
32
36
  trigger,
33
37
  footer,
38
+ headerActions,
39
+ showClose = true,
34
40
  }: Props = $props();
35
41
 
36
42
  function handleOpenChange(newOpen: boolean) {
37
43
  open = newOpen;
38
44
  onOpenChange?.(newOpen);
39
45
  }
46
+
47
+ function handleClose() {
48
+ open = false;
49
+ onOpenChange?.(false);
50
+ }
40
51
  </script>
41
52
 
42
53
  <DialogPrimitive.Root bind:open onOpenChange={handleOpenChange}>
43
54
  {#if trigger}
44
- <DialogPrimitive.Trigger asChild>
55
+ <DialogPrimitive.Trigger>
45
56
  {#snippet child({ props })}
46
57
  <span {...props}>
47
58
  {@render trigger()}
@@ -56,56 +67,74 @@
56
67
  />
57
68
  <DialogPrimitive.Content
58
69
  class={cn(
59
- 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
70
+ 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg overflow-hidden',
60
71
  className
61
72
  )}
62
73
  >
63
- {#if title || description}
64
- <div class="flex flex-col space-y-1.5 text-center sm:text-left">
65
- {#if title}
66
- <DialogPrimitive.Title class="text-lg font-semibold leading-none tracking-tight">
67
- {title}
68
- </DialogPrimitive.Title>
69
- {/if}
70
- {#if description}
71
- <DialogPrimitive.Description class="text-sm text-muted-foreground">
72
- {description}
73
- </DialogPrimitive.Description>
74
- {/if}
74
+ <!-- Header section with title and actions -->
75
+ {#if title || description || headerActions || showClose}
76
+ <div
77
+ class="flex items-start justify-between gap-4 border-b border-border bg-muted/30 px-6 py-4"
78
+ >
79
+ <!-- Title section -->
80
+ <div class="flex flex-col space-y-1.5 min-w-0 flex-1">
81
+ {#if title}
82
+ <DialogPrimitive.Title class="text-lg font-semibold leading-none tracking-tight">
83
+ {title}
84
+ </DialogPrimitive.Title>
85
+ {/if}
86
+ {#if description}
87
+ <DialogPrimitive.Description class="text-sm text-muted-foreground">
88
+ {description}
89
+ </DialogPrimitive.Description>
90
+ {/if}
91
+ </div>
92
+
93
+ <!-- Actions section -->
94
+ <div class="flex items-center gap-1 shrink-0 -mr-2 -mt-1">
95
+ {#if headerActions}
96
+ {@render headerActions()}
97
+ {/if}
98
+ {#if showClose}
99
+ <DialogPrimitive.Close
100
+ class="flex h-9 w-9 items-center justify-center rounded-md bg-transparent text-muted-foreground transition-[background-color,color] duration-150 ease-in-out hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none"
101
+ aria-label="Close dialog"
102
+ >
103
+ <svg
104
+ xmlns="http://www.w3.org/2000/svg"
105
+ width="20"
106
+ height="20"
107
+ viewBox="0 0 24 24"
108
+ fill="none"
109
+ stroke="currentColor"
110
+ stroke-width="2"
111
+ stroke-linecap="round"
112
+ stroke-linejoin="round"
113
+ class="h-5 w-5"
114
+ >
115
+ <path d="M18 6 6 18" />
116
+ <path d="m6 6 12 12" />
117
+ </svg>
118
+ <span class="sr-only">Close</span>
119
+ </DialogPrimitive.Close>
120
+ {/if}
121
+ </div>
75
122
  </div>
76
123
  {/if}
77
124
 
78
- <div class="dialog-content">
125
+ <!-- Content section -->
126
+ <div class="dialog-content px-6 py-2">
79
127
  {@render children()}
80
128
  </div>
81
129
 
130
+ <!-- Footer section -->
82
131
  {#if footer}
83
- <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
132
+ <div
133
+ class="flex flex-col-reverse border-t border-border bg-muted/30 px-6 py-4 sm:flex-row sm:justify-end sm:space-x-2"
134
+ >
84
135
  {@render footer()}
85
136
  </div>
86
137
  {/if}
87
-
88
- <DialogPrimitive.Close
89
- class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
90
- aria-label="Close dialog"
91
- >
92
- <svg
93
- xmlns="http://www.w3.org/2000/svg"
94
- width="24"
95
- height="24"
96
- viewBox="0 0 24 24"
97
- fill="none"
98
- stroke="currentColor"
99
- stroke-width="2"
100
- stroke-linecap="round"
101
- stroke-linejoin="round"
102
- class="h-4 w-4"
103
- >
104
- <path d="M18 6 6 18" />
105
- <path d="m6 6 12 12" />
106
- </svg>
107
- <span class="sr-only">Close</span>
108
- </DialogPrimitive.Close>
109
138
  </DialogPrimitive.Content>
110
139
  </DialogPrimitive.Portal>
111
140
  </DialogPrimitive.Root>
@@ -16,6 +16,10 @@ interface Props {
16
16
  trigger?: Snippet;
17
17
  /** Optional footer element */
18
18
  footer?: Snippet;
19
+ /** Optional header actions (rendered before the close button) */
20
+ headerActions?: Snippet;
21
+ /** Whether to show the close button (default: true) */
22
+ showClose?: boolean;
19
23
  }
20
24
  declare const Dialog: import("svelte").Component<Props, {}, "open">;
21
25
  type Dialog = ReturnType<typeof Dialog>;
@@ -56,7 +56,7 @@
56
56
  </script>
57
57
 
58
58
  <DropdownMenuPrimitive.Root bind:open onOpenChange={handleOpenChange}>
59
- <DropdownMenuPrimitive.Trigger asChild>
59
+ <DropdownMenuPrimitive.Trigger>
60
60
  {#snippet child({ props })}
61
61
  <span {...props}>
62
62
  {@render trigger()}
@@ -93,8 +93,9 @@
93
93
  disabled={subItem.disabled}
94
94
  onSelect={() => subItem.onSelect?.()}
95
95
  class={cn(
96
- 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
97
- subItem.destructive && 'text-destructive focus:text-destructive'
96
+ 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors duration-75 hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default',
97
+ subItem.destructive &&
98
+ 'text-destructive hover:text-destructive focus:text-destructive data-[highlighted]:text-destructive'
98
99
  )}
99
100
  >
100
101
  {subItem.label}
@@ -118,8 +119,9 @@
118
119
  disabled={item.disabled}
119
120
  onSelect={() => item.onSelect?.()}
120
121
  class={cn(
121
- 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
122
- item.destructive && 'text-destructive focus:text-destructive'
122
+ 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors duration-75 hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default',
123
+ item.destructive &&
124
+ 'text-destructive hover:text-destructive focus:text-destructive data-[highlighted]:text-destructive'
123
125
  )}
124
126
  >
125
127
  {item.label}
@@ -9,8 +9,8 @@
9
9
  label: string;
10
10
  /** Input ID for association */
11
11
  id: string;
12
- /** Input type (text, email, password, tel, url, number, textarea) */
13
- type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'number' | 'textarea';
12
+ /** Input type (text, email, password, tel, url, number, date, textarea) */
13
+ type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'number' | 'date' | 'textarea';
14
14
  /** Field value (bindable) */
15
15
  value?: string;
16
16
  /** Error message */
@@ -3,8 +3,8 @@ interface Props {
3
3
  label: string;
4
4
  /** Input ID for association */
5
5
  id: string;
6
- /** Input type (text, email, password, tel, url, number, textarea) */
7
- type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'number' | 'textarea';
6
+ /** Input type (text, email, password, tel, url, number, date, textarea) */
7
+ type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'number' | 'date' | 'textarea';
8
8
  /** Field value (bindable) */
9
9
  value?: string;
10
10
  /** Error message */
@@ -33,6 +33,10 @@
33
33
  class?: string;
34
34
  /** Optional label */
35
35
  label?: string;
36
+ /** Element ID for accessibility */
37
+ id?: string;
38
+ /** Error message to display */
39
+ error?: string;
36
40
  }
37
41
 
38
42
  let {
@@ -45,6 +49,8 @@
45
49
  required = false,
46
50
  class: className,
47
51
  label,
52
+ id,
53
+ error,
48
54
  }: Props = $props();
49
55
 
50
56
  // Validate props in development
@@ -111,11 +117,14 @@
111
117
  onValueChange={handleValueChange}
112
118
  >
113
119
  <SelectPrimitive.Trigger
120
+ {id}
114
121
  class={cn(
115
122
  'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
123
+ error && 'border-destructive focus:ring-destructive',
116
124
  className
117
125
  )}
118
126
  aria-label={label || placeholder}
127
+ aria-invalid={error ? 'true' : undefined}
119
128
  >
120
129
  <span class={value ? '' : 'text-muted-foreground'}>
121
130
  {selectedLabel || placeholder}
@@ -152,8 +161,26 @@
152
161
  value={option.value}
153
162
  label={option.label}
154
163
  disabled={option.disabled}
155
- class="relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
164
+ class="relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors duration-75 hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default"
156
165
  >
166
+ {#if option.value === value}
167
+ <span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
168
+ <svg
169
+ xmlns="http://www.w3.org/2000/svg"
170
+ width="16"
171
+ height="16"
172
+ viewBox="0 0 24 24"
173
+ fill="none"
174
+ stroke="currentColor"
175
+ stroke-width="2"
176
+ stroke-linecap="round"
177
+ stroke-linejoin="round"
178
+ class="h-4 w-4"
179
+ >
180
+ <polyline points="20 6 9 17 4 12" />
181
+ </svg>
182
+ </span>
183
+ {/if}
157
184
  {option.label}
158
185
  </SelectPrimitive.Item>
159
186
  {/each}
@@ -163,8 +190,26 @@
163
190
  value={item.value}
164
191
  label={item.label}
165
192
  disabled={item.disabled}
166
- class="relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
193
+ class="relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors duration-75 hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default"
167
194
  >
195
+ {#if item.value === value}
196
+ <span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
197
+ <svg
198
+ xmlns="http://www.w3.org/2000/svg"
199
+ width="16"
200
+ height="16"
201
+ viewBox="0 0 24 24"
202
+ fill="none"
203
+ stroke="currentColor"
204
+ stroke-width="2"
205
+ stroke-linecap="round"
206
+ stroke-linejoin="round"
207
+ class="h-4 w-4"
208
+ >
209
+ <polyline points="20 6 9 17 4 12" />
210
+ </svg>
211
+ </span>
212
+ {/if}
168
213
  {item.label}
169
214
  </SelectPrimitive.Item>
170
215
  {/if}
@@ -172,3 +217,9 @@
172
217
  </div>
173
218
  </SelectPrimitive.Content>
174
219
  </SelectPrimitive.Root>
220
+
221
+ {#if error}
222
+ <p class="text-sm text-destructive mt-1.5" role="alert">
223
+ {error}
224
+ </p>
225
+ {/if}
@@ -26,6 +26,10 @@ interface Props {
26
26
  class?: string;
27
27
  /** Optional label */
28
28
  label?: string;
29
+ /** Element ID for accessibility */
30
+ id?: string;
31
+ /** Error message to display */
32
+ error?: string;
29
33
  }
30
34
  declare const Select: import("svelte").Component<Props, {}, "value">;
31
35
  type Select = ReturnType<typeof Select>;
@@ -30,7 +30,7 @@
30
30
 
31
31
  <TooltipPrimitive.Provider>
32
32
  <TooltipPrimitive.Root {delayDuration}>
33
- <TooltipPrimitive.Trigger asChild>
33
+ <TooltipPrimitive.Trigger>
34
34
  {#snippet child({ props })}
35
35
  <span {...props} class="inline-flex">
36
36
  {@render children()}
@@ -19,31 +19,8 @@
19
19
  * });
20
20
  * ```
21
21
  */
22
- // Detect development mode
23
- const getIsDev = () => {
24
- try {
25
- // @ts-expect-error - import.meta.env may not exist
26
- if (typeof import.meta !== 'undefined' && import.meta.env?.DEV !== undefined) {
27
- // @ts-expect-error - accessing env.DEV
28
- return import.meta.env.DEV === true;
29
- }
30
- }
31
- catch {
32
- // Ignore
33
- }
34
- try {
35
- // @ts-expect-error - process may not exist
36
- if (typeof process !== 'undefined' && process.env?.NODE_ENV !== undefined) {
37
- // @ts-expect-error - accessing process.env
38
- return process.env.NODE_ENV !== 'production';
39
- }
40
- }
41
- catch {
42
- // Ignore
43
- }
44
- return false;
45
- };
46
- const isDev = getIsDev();
22
+ import { DEV } from 'esm-env';
23
+ const isDev = DEV;
47
24
  const marks = new Map();
48
25
  /** Threshold in ms for logging slow operations (1 frame at 60fps) */
49
26
  const SLOW_THRESHOLD = 16;
@@ -7,16 +7,13 @@
7
7
  /**
8
8
  * Dropdown Menu Types
9
9
  */
10
- import type { Snippet } from 'svelte';
11
10
  export interface DropdownMenuItem {
12
- type?: 'item' | 'checkbox' | 'radio' | 'separator' | 'label';
11
+ type?: 'item' | 'separator' | 'label';
13
12
  label?: string;
14
13
  value?: string;
15
14
  disabled?: boolean;
16
- checked?: boolean;
17
15
  onSelect?: () => void;
18
16
  destructive?: boolean;
19
- icon?: Snippet;
20
17
  shortcut?: string;
21
18
  }
22
19
  export interface DropdownMenuGroup {
@@ -67,7 +64,7 @@ export interface FileMetadata {
67
64
  /**
68
65
  * DataTable Types
69
66
  */
70
- export interface DataTableColumn<T = Record<string, unknown>> {
67
+ export interface DataTableColumn<T = object> {
71
68
  /** Unique column identifier */
72
69
  id: string;
73
70
  /** Column header text */
@@ -4,34 +4,8 @@
4
4
  * These utilities provide development-time warnings for common prop mistakes.
5
5
  * They are designed to be tree-shaken in production builds.
6
6
  */
7
- // Detect development mode in a way that works across different environments
8
- const getIsDev = () => {
9
- try {
10
- // Vite/SvelteKit environment
11
- // @ts-expect-error - import.meta.env may not exist in all environments
12
- if (typeof import.meta !== 'undefined' && import.meta.env?.DEV !== undefined) {
13
- // @ts-expect-error - accessing env.DEV
14
- return import.meta.env.DEV === true;
15
- }
16
- }
17
- catch {
18
- // Ignore errors from import.meta access
19
- }
20
- try {
21
- // Node.js environment
22
- // @ts-expect-error - process may not exist in browser
23
- if (typeof process !== 'undefined' && process.env?.NODE_ENV !== undefined) {
24
- // @ts-expect-error - accessing process.env
25
- return process.env.NODE_ENV !== 'production';
26
- }
27
- }
28
- catch {
29
- // Ignore errors from process access
30
- }
31
- // Default to false (production mode) if we can't determine
32
- return false;
33
- };
34
- const isDev = getIsDev();
7
+ import { DEV } from 'esm-env';
8
+ const isDev = DEV;
35
9
  /**
36
10
  * Validate that a required array prop is non-empty
37
11
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/theme-svelte",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Svelte components for the Classic theme system",
5
5
  "type": "module",
6
6
  "svelte": "./dist/lib/index.js",
@@ -43,6 +43,7 @@
43
43
  "@classic-homes/theme-tokens": "*",
44
44
  "bits-ui": "^1.0.0",
45
45
  "clsx": "^2.1.0",
46
+ "esm-env": "1.2.2",
46
47
  "tailwind-merge": "^2.2.0",
47
48
  "tailwind-variants": "^0.3.0",
48
49
  "zod": "^3.23.0"