@svadmin/ui 0.51.0 → 0.53.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 (33) hide show
  1. package/dist/components/BatchActionBar.svelte +119 -0
  2. package/dist/components/BatchActionBar.svelte.d.ts +15 -0
  3. package/dist/components/FilterDropdown.svelte +252 -0
  4. package/dist/components/FilterDropdown.svelte.d.ts +24 -0
  5. package/dist/components/content/AuditTimeline.svelte +86 -0
  6. package/dist/components/content/AuditTimeline.svelte.d.ts +20 -0
  7. package/dist/components/content/index.d.ts +2 -0
  8. package/dist/components/content/index.js +1 -0
  9. package/dist/components/fields/AvatarField.svelte +123 -0
  10. package/dist/components/fields/AvatarField.svelte.d.ts +16 -0
  11. package/dist/components/fields/BooleanField.svelte +90 -8
  12. package/dist/components/fields/BooleanField.svelte.d.ts +8 -0
  13. package/dist/components/fields/CodeField.svelte +96 -0
  14. package/dist/components/fields/CodeField.svelte.d.ts +13 -0
  15. package/dist/components/fields/CopyField.svelte +102 -0
  16. package/dist/components/fields/CopyField.svelte.d.ts +15 -0
  17. package/dist/components/fields/DateField.svelte +74 -4
  18. package/dist/components/fields/DateField.svelte.d.ts +4 -0
  19. package/dist/components/fields/DateRangeField.svelte +98 -0
  20. package/dist/components/fields/DateRangeField.svelte.d.ts +22 -0
  21. package/dist/components/fields/NumberField.svelte +72 -4
  22. package/dist/components/fields/NumberField.svelte.d.ts +9 -0
  23. package/dist/components/fields/PercentField.svelte +92 -0
  24. package/dist/components/fields/PercentField.svelte.d.ts +13 -0
  25. package/dist/components/fields/RatingField.svelte +76 -0
  26. package/dist/components/fields/RatingField.svelte.d.ts +12 -0
  27. package/dist/components/fields/TagField.svelte +90 -6
  28. package/dist/components/fields/TagField.svelte.d.ts +9 -0
  29. package/dist/components/fields/UrlField.svelte +43 -7
  30. package/dist/components/fields/UrlField.svelte.d.ts +5 -0
  31. package/dist/index.d.ts +17 -0
  32. package/dist/index.js +9 -0
  33. package/package.json +1 -1
@@ -0,0 +1,92 @@
1
+ <script module lang="ts">
2
+ export type PercentTone = 'auto' | 'success' | 'warning' | 'destructive' | 'info' | 'neutral';
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { cn } from '../../utils.js';
7
+
8
+ interface Props {
9
+ value?: number | string | null | undefined;
10
+ precision?: number;
11
+ scale?: '100' | '1';
12
+ showProgress?: boolean;
13
+ tone?: PercentTone;
14
+ nullLabel?: string;
15
+ class?: string;
16
+ }
17
+
18
+ let {
19
+ value,
20
+ precision = 1,
21
+ scale = '100',
22
+ showProgress = false,
23
+ tone = 'neutral',
24
+ nullLabel = '—',
25
+ class: className = '',
26
+ }: Props = $props();
27
+
28
+ const numericValue = $derived.by(() => {
29
+ if (value == null || value === '') return null;
30
+ const num = typeof value === 'number' ? value : Number(value);
31
+ return isNaN(num) ? null : num;
32
+ });
33
+
34
+ const percentNumber = $derived.by(() => {
35
+ if (numericValue === null) return null;
36
+ return scale === '1' ? numericValue * 100 : numericValue;
37
+ });
38
+
39
+ const formatted = $derived.by(() => {
40
+ if (percentNumber === null) return nullLabel;
41
+ return `${percentNumber.toFixed(precision)}%`;
42
+ });
43
+
44
+ const resolvedTone = $derived.by(() => {
45
+ if (tone !== 'auto') return tone;
46
+ if (percentNumber === null) return 'neutral';
47
+ if (percentNumber >= 80) return 'success';
48
+ if (percentNumber >= 50) return 'warning';
49
+ return 'destructive';
50
+ });
51
+
52
+ const toneTextClass: Record<PercentTone, string> = {
53
+ auto: '',
54
+ success: 'text-success font-medium',
55
+ warning: 'text-warning-foreground font-medium',
56
+ destructive: 'text-destructive font-medium',
57
+ info: 'text-primary font-medium',
58
+ neutral: 'text-foreground',
59
+ };
60
+
61
+ const toneBarClass: Record<PercentTone, string> = {
62
+ auto: '',
63
+ success: 'bg-success',
64
+ warning: 'bg-warning',
65
+ destructive: 'bg-destructive',
66
+ info: 'bg-primary',
67
+ neutral: 'bg-primary',
68
+ };
69
+
70
+ const clampedProgress = $derived.by(() => {
71
+ if (percentNumber === null) return 0;
72
+ return Math.min(100, Math.max(0, percentNumber));
73
+ });
74
+ </script>
75
+
76
+ {#if numericValue === null}
77
+ <span class={cn('field-percent text-muted-foreground text-sm', className)}>{nullLabel}</span>
78
+ {:else}
79
+ <div class={cn('field-percent inline-flex items-center gap-2 text-sm', className)}>
80
+ <span class={cn('tabular-nums', toneTextClass[resolvedTone])}>
81
+ {formatted}
82
+ </span>
83
+ {#if showProgress}
84
+ <div class="h-1.5 w-16 overflow-hidden rounded-full bg-muted">
85
+ <div
86
+ class={cn('h-full transition-all duration-300', toneBarClass[resolvedTone])}
87
+ style="width: {clampedProgress}%"
88
+ ></div>
89
+ </div>
90
+ {/if}
91
+ </div>
92
+ {/if}
@@ -0,0 +1,13 @@
1
+ export type PercentTone = 'auto' | 'success' | 'warning' | 'destructive' | 'info' | 'neutral';
2
+ interface Props {
3
+ value?: number | string | null | undefined;
4
+ precision?: number;
5
+ scale?: '100' | '1';
6
+ showProgress?: boolean;
7
+ tone?: PercentTone;
8
+ nullLabel?: string;
9
+ class?: string;
10
+ }
11
+ declare const PercentField: import("svelte").Component<Props, {}, "">;
12
+ type PercentField = ReturnType<typeof PercentField>;
13
+ export default PercentField;
@@ -0,0 +1,76 @@
1
+ <script module lang="ts">
2
+ export type RatingSize = 'sm' | 'default' | 'lg';
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { Star, StarHalf } from '@lucide/svelte';
7
+ import { cn } from '../../utils.js';
8
+
9
+ interface Props {
10
+ value?: number | string | null | undefined;
11
+ max?: number;
12
+ showValue?: boolean;
13
+ size?: RatingSize;
14
+ nullLabel?: string;
15
+ class?: string;
16
+ }
17
+
18
+ let {
19
+ value,
20
+ max = 5,
21
+ showValue = false,
22
+ size = 'default',
23
+ nullLabel = '—',
24
+ class: className = '',
25
+ }: Props = $props();
26
+
27
+ const numericValue = $derived.by(() => {
28
+ if (value == null || value === '') return null;
29
+ const num = typeof value === 'number' ? value : Number(value);
30
+ return isNaN(num) ? null : Math.max(0, Math.min(max, num));
31
+ });
32
+
33
+ const sizeClasses: Record<RatingSize, { icon: string; text: string }> = {
34
+ sm: { icon: 'h-3.5 w-3.5', text: 'text-xs' },
35
+ default: { icon: 'h-4 w-4', text: 'text-sm' },
36
+ lg: { icon: 'h-5 w-5', text: 'text-base' },
37
+ };
38
+
39
+ const stars = $derived.by(() => {
40
+ if (numericValue === null) return [];
41
+ const result: ('full' | 'half' | 'empty')[] = [];
42
+ for (let i = 1; i <= max; i++) {
43
+ if (numericValue >= i) {
44
+ result.push('full');
45
+ } else if (numericValue >= i - 0.5) {
46
+ result.push('half');
47
+ } else {
48
+ result.push('empty');
49
+ }
50
+ }
51
+ return result;
52
+ });
53
+ </script>
54
+
55
+ {#if numericValue === null}
56
+ <span class={cn('field-rating text-muted-foreground text-sm', className)}>{nullLabel}</span>
57
+ {:else}
58
+ <div class={cn('field-rating inline-flex items-center gap-1.5', className)}>
59
+ <div class="inline-flex items-center gap-0.5 text-amber-500">
60
+ {#each stars as starType, idx (idx)}
61
+ {#if starType === 'full'}
62
+ <Star class={cn(sizeClasses[size].icon, 'fill-amber-500 text-amber-500')} />
63
+ {:else if starType === 'half'}
64
+ <StarHalf class={cn(sizeClasses[size].icon, 'fill-amber-500 text-amber-500')} />
65
+ {:else}
66
+ <Star class={cn(sizeClasses[size].icon, 'text-muted-foreground/30')} />
67
+ {/if}
68
+ {/each}
69
+ </div>
70
+ {#if showValue}
71
+ <span class={cn('tabular-nums font-medium text-foreground', sizeClasses[size].text)}>
72
+ {numericValue}
73
+ </span>
74
+ {/if}
75
+ </div>
76
+ {/if}
@@ -0,0 +1,12 @@
1
+ export type RatingSize = 'sm' | 'default' | 'lg';
2
+ interface Props {
3
+ value?: number | string | null | undefined;
4
+ max?: number;
5
+ showValue?: boolean;
6
+ size?: RatingSize;
7
+ nullLabel?: string;
8
+ class?: string;
9
+ }
10
+ declare const RatingField: import("svelte").Component<Props, {}, "">;
11
+ type RatingField = ReturnType<typeof RatingField>;
12
+ export default RatingField;
@@ -1,12 +1,96 @@
1
+ <script module lang="ts">
2
+ export type TagTone = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import { Badge } from '../ui/badge/index.js';
3
- interface Props { value: string | string[] | null | undefined }
4
- let { value }: Props = $props();
5
- const tags = $derived(Array.isArray(value) ? value : value ? [value] : []);
7
+ import { cn } from '../../utils.js';
8
+
9
+ interface Props {
10
+ value: string | string[] | null | undefined;
11
+ colorMap?: Record<string, TagTone | string>;
12
+ tone?: TagTone;
13
+ variant?: 'secondary' | 'outline' | 'default' | 'destructive';
14
+ size?: 'sm' | 'default';
15
+ clickable?: boolean;
16
+ onclick?: (tag: string) => void;
17
+ nullLabel?: string;
18
+ class?: string;
19
+ }
20
+
21
+ let {
22
+ value,
23
+ colorMap,
24
+ tone,
25
+ variant = 'secondary',
26
+ size = 'default',
27
+ clickable = false,
28
+ onclick,
29
+ nullLabel = '—',
30
+ class: className = '',
31
+ }: Props = $props();
32
+
33
+ const toneClass: Record<TagTone, string> = {
34
+ success: 'border-success/30 bg-success/10 text-success',
35
+ warning: 'border-warning/30 bg-warning/10 text-warning-foreground',
36
+ danger: 'border-destructive/30 bg-destructive/10 text-destructive',
37
+ info: 'border-primary/30 bg-primary/10 text-primary',
38
+ neutral: 'border-border bg-muted text-muted-foreground',
39
+ };
40
+
41
+ const tags = $derived(
42
+ Array.isArray(value)
43
+ ? value.filter((t): t is string => t != null && t !== '')
44
+ : value != null && value !== ''
45
+ ? [String(value)]
46
+ : []
47
+ );
48
+
49
+ function resolveTagClass(tag: string): string {
50
+ const mapped = colorMap?.[tag] ?? tone;
51
+ if (mapped && mapped in toneClass) {
52
+ return toneClass[mapped as TagTone];
53
+ }
54
+ if (typeof mapped === 'string') {
55
+ return mapped;
56
+ }
57
+ return '';
58
+ }
6
59
  </script>
7
- <span class="inline-flex flex-wrap gap-1">
60
+
61
+ <span class={cn('inline-flex flex-wrap gap-1 items-center', className)}>
8
62
  {#each tags as tag, _i (_i)}
9
- <Badge variant="secondary">{tag}</Badge>
63
+ {@const customClass = resolveTagClass(tag)}
64
+ {#if clickable || onclick}
65
+ <button
66
+ type="button"
67
+ onclick={() => onclick?.(tag)}
68
+ class="focus:outline-none focus:ring-1 focus:ring-ring rounded"
69
+ >
70
+ <Badge
71
+ variant={customClass ? 'outline' : variant}
72
+ class={cn(
73
+ 'transition-colors cursor-pointer hover:opacity-80',
74
+ size === 'sm' ? 'px-1.5 py-0 text-[11px] h-5' : '',
75
+ customClass
76
+ )}
77
+ >
78
+ {tag}
79
+ </Badge>
80
+ </button>
81
+ {:else}
82
+ <Badge
83
+ variant={customClass ? 'outline' : variant}
84
+ class={cn(
85
+ size === 'sm' ? 'px-1.5 py-0 text-[11px] h-5' : '',
86
+ customClass
87
+ )}
88
+ >
89
+ {tag}
90
+ </Badge>
91
+ {/if}
10
92
  {/each}
11
- {#if tags.length === 0}<span class="text-muted-foreground">—</span>{/if}
93
+ {#if tags.length === 0}
94
+ <span class="text-muted-foreground">{nullLabel}</span>
95
+ {/if}
12
96
  </span>
@@ -1,5 +1,14 @@
1
+ export type TagTone = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
1
2
  interface Props {
2
3
  value: string | string[] | null | undefined;
4
+ colorMap?: Record<string, TagTone | string>;
5
+ tone?: TagTone;
6
+ variant?: 'secondary' | 'outline' | 'default' | 'destructive';
7
+ size?: 'sm' | 'default';
8
+ clickable?: boolean;
9
+ onclick?: (tag: string) => void;
10
+ nullLabel?: string;
11
+ class?: string;
3
12
  }
4
13
  declare const TagField: import("svelte").Component<Props, {}, "">;
5
14
  type TagField = ReturnType<typeof TagField>;
@@ -1,14 +1,50 @@
1
1
  <script lang="ts">
2
2
  import { ExternalLink } from '@lucide/svelte';
3
- interface Props { value: string | null | undefined; label?: string }
4
- let { value, label }: Props = $props();
5
- const display = $derived(label ?? value ?? '');
3
+ import { cn } from '../../utils.js';
4
+
5
+ interface Props {
6
+ value: string | null | undefined;
7
+ label?: string;
8
+ target?: string;
9
+ maxDisplayLength?: number;
10
+ showIcon?: boolean;
11
+ nullLabel?: string;
12
+ class?: string;
13
+ }
14
+
15
+ let {
16
+ value,
17
+ label,
18
+ target = '_blank',
19
+ maxDisplayLength = 40,
20
+ showIcon = true,
21
+ nullLabel = '—',
22
+ class: className = '',
23
+ }: Props = $props();
24
+
25
+ const display = $derived.by(() => {
26
+ if (label) return label;
27
+ if (!value) return '';
28
+ if (maxDisplayLength && value.length > maxDisplayLength) {
29
+ return value.slice(0, maxDisplayLength) + '…';
30
+ }
31
+ return value;
32
+ });
6
33
  </script>
34
+
7
35
  {#if value}
8
- <a href={value} target="_blank" rel="noopener noreferrer" class="text-primary hover:underline no-underline inline-flex items-center gap-1">
9
- {display}
10
- <ExternalLink class="h-3 w-3 opacity-60" />
36
+ <a
37
+ href={value}
38
+ {target}
39
+ rel="noopener noreferrer"
40
+ class={cn('text-primary hover:underline inline-flex items-center gap-1 text-sm font-medium', className)}
41
+ title={value}
42
+ >
43
+ <span>{display}</span>
44
+ {#if showIcon}
45
+ <ExternalLink class="h-3 w-3 shrink-0 opacity-60" />
46
+ {/if}
11
47
  </a>
12
48
  {:else}
13
- <span class="text-muted-foreground">—</span>
49
+ <span class="text-muted-foreground">{nullLabel}</span>
14
50
  {/if}
@@ -1,6 +1,11 @@
1
1
  interface Props {
2
2
  value: string | null | undefined;
3
3
  label?: string;
4
+ target?: string;
5
+ maxDisplayLength?: number;
6
+ showIcon?: boolean;
7
+ nullLabel?: string;
8
+ class?: string;
4
9
  }
5
10
  declare const UrlField: import("svelte").Component<Props, {}, "">;
6
11
  type UrlField = ReturnType<typeof UrlField>;
package/dist/index.d.ts CHANGED
@@ -103,8 +103,14 @@ export { default as ImageField } from './components/fields/ImageField.svelte';
103
103
  export { default as SelectField } from './components/fields/SelectField.svelte';
104
104
  export { default as MultiSelectField } from './components/fields/MultiSelectField.svelte';
105
105
  export { default as RelationField } from './components/fields/RelationField.svelte';
106
+ export { default as DateRangeField } from './components/fields/DateRangeField.svelte';
106
107
  export { default as JsonField } from './components/fields/JsonField.svelte';
107
108
  export { default as RichTextField } from './components/fields/RichTextField.svelte';
109
+ export { default as CopyField } from './components/fields/CopyField.svelte';
110
+ export { default as AvatarField } from './components/fields/AvatarField.svelte';
111
+ export { default as CodeField } from './components/fields/CodeField.svelte';
112
+ export { default as PercentField } from './components/fields/PercentField.svelte';
113
+ export { default as RatingField } from './components/fields/RatingField.svelte';
108
114
  export { CreateButton, EditButton, DeleteButton, ShowButton, ListButton, RefreshButton, ExportButton, ImportButton, SaveButton, CloneButton, } from './components/buttons/index.js';
109
115
  export { Button, buttonVariants } from './components/ui/button/index.js';
110
116
  export { Input } from './components/ui/input/index.js';
@@ -204,3 +210,14 @@ export type { DemoMember, DemoProject, DemoTeam, DemoIntegration, DemoApiKey, De
204
210
  export { default as RowActions } from './components/RowActions.svelte';
205
211
  export type { RowActionItem } from './components/RowActions.svelte';
206
212
  export { default as DetailDrawer } from './components/DetailDrawer.svelte';
213
+ export { default as AuditTimeline } from './components/content/AuditTimeline.svelte';
214
+ export type { TimelineItem } from './components/content/AuditTimeline.svelte';
215
+ export type { TagTone } from './components/fields/TagField.svelte';
216
+ export type { BooleanTone } from './components/fields/BooleanField.svelte';
217
+ export type { DateFieldFormat } from './components/fields/DateField.svelte';
218
+ export type { AvatarStatus, AvatarSize } from './components/fields/AvatarField.svelte';
219
+ export { default as FilterDropdown } from './components/FilterDropdown.svelte';
220
+ export { default as BatchActionBar } from './components/BatchActionBar.svelte';
221
+ export type { DateRangeFormat, DateRangeValue } from './components/fields/DateRangeField.svelte';
222
+ export type { PercentTone } from './components/fields/PercentField.svelte';
223
+ export type { RatingSize } from './components/fields/RatingField.svelte';
package/dist/index.js CHANGED
@@ -106,8 +106,14 @@ export { default as ImageField } from './components/fields/ImageField.svelte';
106
106
  export { default as SelectField } from './components/fields/SelectField.svelte';
107
107
  export { default as MultiSelectField } from './components/fields/MultiSelectField.svelte';
108
108
  export { default as RelationField } from './components/fields/RelationField.svelte';
109
+ export { default as DateRangeField } from './components/fields/DateRangeField.svelte';
109
110
  export { default as JsonField } from './components/fields/JsonField.svelte';
110
111
  export { default as RichTextField } from './components/fields/RichTextField.svelte';
112
+ export { default as CopyField } from './components/fields/CopyField.svelte';
113
+ export { default as AvatarField } from './components/fields/AvatarField.svelte';
114
+ export { default as CodeField } from './components/fields/CodeField.svelte';
115
+ export { default as PercentField } from './components/fields/PercentField.svelte';
116
+ export { default as RatingField } from './components/fields/RatingField.svelte';
111
117
  // Rich text editor (requires @svadmin/editor) as a direct import in consumer app
112
118
  // CRUD Buttons
113
119
  export { CreateButton, EditButton, DeleteButton, ShowButton, ListButton, RefreshButton, ExportButton, ImportButton, SaveButton, CloneButton, } from './components/buttons/index.js';
@@ -202,3 +208,6 @@ export { default as SystemErrorState } from './components/content/SystemErrorSta
202
208
  export { referenceDemoData } from './reference-data.js';
203
209
  export { default as RowActions } from './components/RowActions.svelte';
204
210
  export { default as DetailDrawer } from './components/DetailDrawer.svelte';
211
+ export { default as AuditTimeline } from './components/content/AuditTimeline.svelte';
212
+ export { default as FilterDropdown } from './components/FilterDropdown.svelte';
213
+ export { default as BatchActionBar } from './components/BatchActionBar.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [