@mrintel/villain-ui 0.3.0 → 0.6.3

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 (128) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3490 -1296
  3. package/dist/components/buttons/Button.svelte +27 -33
  4. package/dist/components/buttons/Button.svelte.d.ts +4 -1
  5. package/dist/components/buttons/ButtonGroup.svelte +17 -30
  6. package/dist/components/buttons/FloatingActionButton.svelte +20 -44
  7. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
  8. package/dist/components/buttons/IconButton.svelte +23 -53
  9. package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
  10. package/dist/components/buttons/LinkButton.svelte +24 -37
  11. package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
  12. package/dist/components/buttons/buttonClasses.d.ts +5 -0
  13. package/dist/components/buttons/buttonClasses.js +8 -3
  14. package/dist/components/cards/Card.svelte +60 -46
  15. package/dist/components/cards/Card.svelte.d.ts +6 -2
  16. package/dist/components/cards/Container.svelte +17 -33
  17. package/dist/components/cards/Divider.svelte +36 -52
  18. package/dist/components/cards/Divider.svelte.d.ts +2 -0
  19. package/dist/components/cards/Grid.svelte +55 -44
  20. package/dist/components/cards/Panel.svelte +18 -32
  21. package/dist/components/cards/Panel.svelte.d.ts +2 -1
  22. package/dist/components/cards/SectionHeader.svelte +24 -38
  23. package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
  24. package/dist/components/data/Avatar.svelte +48 -67
  25. package/dist/components/data/Badge.svelte +45 -32
  26. package/dist/components/data/Badge.svelte.d.ts +7 -1
  27. package/dist/components/data/CalendarGrid.svelte +433 -0
  28. package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
  29. package/dist/components/data/CalendarGrid.types.d.ts +7 -0
  30. package/dist/components/data/CalendarGrid.types.js +1 -0
  31. package/dist/components/data/CodeBlock.svelte +119 -121
  32. package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
  33. package/dist/components/data/List.svelte +87 -64
  34. package/dist/components/data/List.svelte.d.ts +7 -0
  35. package/dist/components/data/Pagination.svelte +121 -123
  36. package/dist/components/data/Pagination.svelte.d.ts +5 -0
  37. package/dist/components/data/Sparkline.svelte +117 -0
  38. package/dist/components/data/Sparkline.svelte.d.ts +43 -0
  39. package/dist/components/data/Stat.svelte +92 -103
  40. package/dist/components/data/Table.svelte +443 -76
  41. package/dist/components/data/Table.svelte.d.ts +23 -2
  42. package/dist/components/data/Table.types.d.ts +14 -0
  43. package/dist/components/data/Table.types.js +1 -0
  44. package/dist/components/data/Tag.svelte +51 -53
  45. package/dist/components/data/Tag.svelte.d.ts +5 -1
  46. package/dist/components/data/index.d.ts +4 -0
  47. package/dist/components/data/index.js +2 -0
  48. package/dist/components/forms/Checkbox.svelte +39 -51
  49. package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
  50. package/dist/components/forms/DatePicker.svelte +61 -0
  51. package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
  52. package/dist/components/forms/DateTimePicker.svelte +63 -0
  53. package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
  54. package/dist/components/forms/FileUpload.svelte +136 -164
  55. package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
  56. package/dist/components/forms/Input.svelte +282 -57
  57. package/dist/components/forms/Input.svelte.d.ts +9 -3
  58. package/dist/components/forms/InputGroup.svelte +7 -7
  59. package/dist/components/forms/RadioGroup.svelte +77 -87
  60. package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
  61. package/dist/components/forms/RangeSlider.svelte +90 -116
  62. package/dist/components/forms/Select.svelte +106 -71
  63. package/dist/components/forms/Select.svelte.d.ts +3 -1
  64. package/dist/components/forms/Switch.svelte +44 -56
  65. package/dist/components/forms/Switch.svelte.d.ts +3 -1
  66. package/dist/components/forms/Textarea.svelte +52 -57
  67. package/dist/components/forms/Textarea.svelte.d.ts +3 -1
  68. package/dist/components/forms/TimePicker.svelte +63 -0
  69. package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
  70. package/dist/components/forms/formClasses.d.ts +3 -0
  71. package/dist/components/forms/formClasses.js +3 -0
  72. package/dist/components/forms/index.d.ts +3 -0
  73. package/dist/components/forms/index.js +3 -0
  74. package/dist/components/navigation/Breadcrumbs.svelte +56 -59
  75. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
  76. package/dist/components/navigation/ContextMenu.svelte +133 -83
  77. package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
  78. package/dist/components/navigation/DropdownMenu.svelte +139 -80
  79. package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
  80. package/dist/components/navigation/Menu.svelte +72 -48
  81. package/dist/components/navigation/Navbar.svelte +111 -32
  82. package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
  83. package/dist/components/navigation/Sidebar.svelte +236 -35
  84. package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
  85. package/dist/components/navigation/Tabs.svelte +86 -54
  86. package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
  87. package/dist/components/overlays/Alert.svelte +81 -99
  88. package/dist/components/overlays/Alert.svelte.d.ts +5 -1
  89. package/dist/components/overlays/CommandPalette.svelte +182 -217
  90. package/dist/components/overlays/Drawer.svelte +158 -167
  91. package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
  92. package/dist/components/overlays/Dropdown.svelte +62 -30
  93. package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
  94. package/dist/components/overlays/Modal.svelte +125 -130
  95. package/dist/components/overlays/Modal.svelte.d.ts +3 -1
  96. package/dist/components/overlays/Popover.svelte +106 -131
  97. package/dist/components/overlays/ProgressBar.svelte +29 -45
  98. package/dist/components/overlays/SkeletonLoader.svelte +66 -82
  99. package/dist/components/overlays/Spinner.svelte +33 -43
  100. package/dist/components/overlays/Toast.svelte +111 -140
  101. package/dist/components/overlays/Toast.svelte.d.ts +3 -0
  102. package/dist/components/overlays/Tooltip.svelte +94 -115
  103. package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
  104. package/dist/components/typography/Code.svelte +10 -14
  105. package/dist/components/typography/Heading.svelte +15 -22
  106. package/dist/components/typography/Heading.svelte.d.ts +1 -0
  107. package/dist/components/typography/Text.svelte +21 -24
  108. package/dist/components/typography/Text.svelte.d.ts +2 -1
  109. package/dist/components/utilities/Accordion.svelte +54 -67
  110. package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
  111. package/dist/components/utilities/Carousel.svelte +124 -152
  112. package/dist/components/utilities/Collapse.svelte +46 -60
  113. package/dist/components/utilities/Hero.svelte +42 -0
  114. package/dist/components/utilities/Hero.svelte.d.ts +10 -0
  115. package/dist/components/utilities/Portal.svelte +47 -72
  116. package/dist/components/utilities/ScrollArea.svelte +33 -41
  117. package/dist/components/utilities/SystemConsole.svelte +310 -0
  118. package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
  119. package/dist/components/utilities/SystemInterface.svelte +726 -0
  120. package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
  121. package/dist/components/utilities/index.d.ts +4 -0
  122. package/dist/components/utilities/index.js +3 -0
  123. package/dist/components/utilities/utilities.types.d.ts +46 -0
  124. package/dist/components/utilities/utilities.types.js +4 -0
  125. package/dist/index.d.ts +49 -4
  126. package/dist/index.js +4 -4
  127. package/dist/theme.css +2821 -218
  128. package/package.json +83 -76
@@ -1,4 +1,5 @@
1
1
  export { default as Table } from './Table.svelte';
2
+ export type { Column as TableColumn, SortDirection, RowKey, SelectionState } from './Table.types';
2
3
  export { default as Pagination } from './Pagination.svelte';
3
4
  export { default as Badge } from './Badge.svelte';
4
5
  export { default as Tag } from './Tag.svelte';
@@ -6,3 +7,6 @@ export { default as List } from './List.svelte';
6
7
  export { default as Avatar } from './Avatar.svelte';
7
8
  export { default as CodeBlock } from './CodeBlock.svelte';
8
9
  export { default as Stat } from './Stat.svelte';
10
+ export { default as CalendarGrid } from './CalendarGrid.svelte';
11
+ export type { CalendarEvent } from './CalendarGrid.types';
12
+ export { default as Sparkline } from './Sparkline.svelte';
@@ -6,3 +6,5 @@ export { default as List } from './List.svelte';
6
6
  export { default as Avatar } from './Avatar.svelte';
7
7
  export { default as CodeBlock } from './CodeBlock.svelte';
8
8
  export { default as Stat } from './Stat.svelte';
9
+ export { default as CalendarGrid } from './CalendarGrid.svelte';
10
+ export { default as Sparkline } from './Sparkline.svelte';
@@ -1,51 +1,39 @@
1
- <script lang="ts">
2
- import { createId } from '../../lib/internal/id.js';
3
-
4
- interface Props {
5
- checked?: boolean;
6
- disabled?: boolean;
7
- label?: string;
8
- id?: string;
9
- onchange?: (event: Event) => void;
10
- }
11
-
12
- let {
13
- checked = $bindable(false),
14
- disabled = false,
15
- label,
16
- id = createId('checkbox'),
17
- onchange
18
- }: Props = $props();
19
- </script>
20
-
21
- <label for={id} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''}">
22
- <input
23
- type="checkbox"
24
- {id}
25
- {disabled}
26
- bind:checked
27
- onchange={onchange}
28
- class="w-5 h-5 rounded-sm border-2 border-border-strong bg-transparent appearance-none transition-all duration-200 ease-luxe cursor-pointer checked:bg-accent checked:border-accent checked:accent-glow focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {disabled ? 'cursor-not-allowed' : ''}"
29
- />
30
- {#if label}
31
- <span class="text-text text-sm select-none">
32
- {label}
33
- </span>
34
- {/if}
35
- </label>
36
-
37
- <style>
38
- input[type="checkbox"]:checked::after {
39
- content: '';
40
- position: absolute;
41
- left: 50%;
42
- top: 50%;
43
- transform: translate(-50%, -50%);
44
- width: 10px;
45
- height: 10px;
46
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none'%3E%3Cpath d='M2 5L4 7L8 3' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
47
- background-size: contain;
48
- background-repeat: no-repeat;
49
- background-position: center;
50
- }
51
- </style>
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ let { checked = $bindable(false), disabled = false, label, id = createId('checkbox'), onchange, iconBefore, class: className = '' } = $props();
3
+ </script>
4
+
5
+ <label for={id} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''} {className}">
6
+ <input
7
+ type="checkbox"
8
+ {id}
9
+ {disabled}
10
+ bind:checked
11
+ onchange={onchange}
12
+ class="w-6 h-6 rounded-sm border-2 border-border-strong bg-transparent appearance-none transition-all duration-200 ease-luxe cursor-pointer checked:bg-accent checked:border-accent checked:accent-glow focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {disabled ? 'cursor-not-allowed' : ''}"
13
+ />
14
+ {#if iconBefore}
15
+ <span class="inline-flex items-center justify-center text-text-soft">
16
+ {@render iconBefore()}
17
+ </span>
18
+ {/if}
19
+ {#if label}
20
+ <span class="text-text text-sm select-none">
21
+ {label}
22
+ </span>
23
+ {/if}
24
+ </label>
25
+
26
+ <style>
27
+ input[type="checkbox"]:checked::after {
28
+ content: '';
29
+ position: absolute;
30
+ left: 50%;
31
+ top: 50%;
32
+ transform: translate(-50%, -50%);
33
+ width: 12px;
34
+ height: 12px;
35
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2.5 6L5 8.5L9.5 3.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
36
+ background-size: contain;
37
+ background-repeat: no-repeat;
38
+ background-position: center;
39
+ }</style>
@@ -1,9 +1,11 @@
1
- interface Props {
1
+ export interface Props {
2
2
  checked?: boolean;
3
3
  disabled?: boolean;
4
4
  label?: string;
5
5
  id?: string;
6
6
  onchange?: (event: Event) => void;
7
+ iconBefore?: import('svelte').Snippet;
8
+ class?: string;
7
9
  }
8
10
  declare const Checkbox: import("svelte").Component<Props, {}, "checked">;
9
11
  type Checkbox = ReturnType<typeof Checkbox>;
@@ -0,0 +1,61 @@
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
3
+ let { value = $bindable(''), min, max, placeholder, disabled = false, error = false, label, id = createId('datepicker'), onchange, class: className = '', } = $props();
4
+ const errorClasses = $derived(error ? 'error-state' : '');
5
+ </script>
6
+
7
+ {#if label}
8
+ <div>
9
+ <label for={id} class="text-text-soft text-sm mb-2 block">
10
+ {label}
11
+ </label>
12
+ <input
13
+ type="date"
14
+ {id}
15
+ {placeholder}
16
+ {disabled}
17
+ {min}
18
+ {max}
19
+ bind:value
20
+ {onchange}
21
+ class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
22
+ ? disabledClasses
23
+ : ''} {className}"
24
+ />
25
+ </div>
26
+ {:else}
27
+ <input
28
+ type="date"
29
+ {id}
30
+ {placeholder}
31
+ {disabled}
32
+ {min}
33
+ {max}
34
+ bind:value
35
+ {onchange}
36
+ class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
37
+ ? disabledClasses
38
+ : ''} {className}"
39
+ />
40
+ {/if}
41
+
42
+ <style>
43
+ input[type='date'] {
44
+ min-height: 3rem;
45
+ cursor: pointer;
46
+ }
47
+
48
+ input[type='date']::-webkit-calendar-picker-indicator {
49
+ cursor: pointer;
50
+ filter: invert(0.7);
51
+ transition: filter 120ms var(--ease-sharp);
52
+ }
53
+
54
+ input[type='date']:hover::-webkit-calendar-picker-indicator {
55
+ filter: invert(1);
56
+ }
57
+
58
+ input[type='date']:disabled::-webkit-calendar-picker-indicator {
59
+ cursor: not-allowed;
60
+ opacity: 0.4;
61
+ }</style>
@@ -0,0 +1,15 @@
1
+ export interface Props {
2
+ value?: string;
3
+ min?: string;
4
+ max?: string;
5
+ placeholder?: string;
6
+ disabled?: boolean;
7
+ error?: boolean;
8
+ label?: string;
9
+ id?: string;
10
+ onchange?: (event: Event) => void;
11
+ class?: string;
12
+ }
13
+ declare const DatePicker: import("svelte").Component<Props, {}, "value">;
14
+ type DatePicker = ReturnType<typeof DatePicker>;
15
+ export default DatePicker;
@@ -0,0 +1,63 @@
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
3
+ let { value = $bindable(''), min, max, step, placeholder, disabled = false, error = false, label, id = createId('datetimepicker'), onchange, class: className = '', } = $props();
4
+ const errorClasses = $derived(error ? 'error-state' : '');
5
+ </script>
6
+
7
+ {#if label}
8
+ <div>
9
+ <label for={id} class="text-text-soft text-sm mb-2 block">
10
+ {label}
11
+ </label>
12
+ <input
13
+ type="datetime-local"
14
+ {id}
15
+ {placeholder}
16
+ {disabled}
17
+ {min}
18
+ {max}
19
+ {step}
20
+ bind:value
21
+ {onchange}
22
+ class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
23
+ ? disabledClasses
24
+ : ''} {className}"
25
+ />
26
+ </div>
27
+ {:else}
28
+ <input
29
+ type="datetime-local"
30
+ {id}
31
+ {placeholder}
32
+ {disabled}
33
+ {min}
34
+ {max}
35
+ {step}
36
+ bind:value
37
+ {onchange}
38
+ class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
39
+ ? disabledClasses
40
+ : ''} {className}"
41
+ />
42
+ {/if}
43
+
44
+ <style>
45
+ input[type='datetime-local'] {
46
+ min-height: 3rem;
47
+ cursor: pointer;
48
+ }
49
+
50
+ input[type='datetime-local']::-webkit-calendar-picker-indicator {
51
+ cursor: pointer;
52
+ filter: invert(0.7);
53
+ transition: filter 120ms var(--ease-sharp);
54
+ }
55
+
56
+ input[type='datetime-local']:hover::-webkit-calendar-picker-indicator {
57
+ filter: invert(1);
58
+ }
59
+
60
+ input[type='datetime-local']:disabled::-webkit-calendar-picker-indicator {
61
+ cursor: not-allowed;
62
+ opacity: 0.4;
63
+ }</style>
@@ -0,0 +1,16 @@
1
+ export interface Props {
2
+ value?: string;
3
+ min?: string;
4
+ max?: string;
5
+ step?: number;
6
+ placeholder?: string;
7
+ disabled?: boolean;
8
+ error?: boolean;
9
+ label?: string;
10
+ id?: string;
11
+ onchange?: (event: Event) => void;
12
+ class?: string;
13
+ }
14
+ declare const DateTimePicker: import("svelte").Component<Props, {}, "value">;
15
+ type DateTimePicker = ReturnType<typeof DateTimePicker>;
16
+ export default DateTimePicker;
@@ -1,164 +1,136 @@
1
- <script lang="ts">
2
- import { createId } from '../../lib/internal/id.js';
3
-
4
- /**
5
- * FileUpload component props.
6
- *
7
- * The `onchange` callback receives an event object with the following shape:
8
- * `{ target: { files: FileList } }` for both drag-and-drop and click-based selection.
9
- */
10
- interface Props {
11
- files?: FileList | null;
12
- accept?: string;
13
- multiple?: boolean;
14
- disabled?: boolean;
15
- label?: string;
16
- id?: string;
17
- onchange?: (event: { target: { files: FileList } }) => void;
18
- }
19
-
20
- let {
21
- files = $bindable(null),
22
- accept,
23
- multiple = false,
24
- disabled = false,
25
- label,
26
- id = createId('file-upload'),
27
- onchange
28
- }: Props = $props();
29
-
30
- let isDragging = $state(false);
31
- let inputElement: HTMLInputElement;
32
-
33
- function handleDragOver(event: DragEvent) {
34
- event.preventDefault();
35
- if (!disabled) {
36
- isDragging = true;
37
- }
38
- }
39
-
40
- function handleDragLeave() {
41
- isDragging = false;
42
- }
43
-
44
- function handleDrop(event: DragEvent) {
45
- event.preventDefault();
46
- isDragging = false;
47
-
48
- if (!disabled && event.dataTransfer?.files) {
49
- files = event.dataTransfer.files;
50
- if (onchange) {
51
- // Call onchange directly with synthetic event object
52
- onchange({ target: { files: event.dataTransfer.files } });
53
- }
54
- }
55
- }
56
-
57
- function handleClick() {
58
- if (!disabled) {
59
- inputElement?.click();
60
- }
61
- }
62
-
63
- function handleInputChange(event: Event) {
64
- const input = event.target as HTMLInputElement;
65
- if (onchange && input.files) {
66
- // Call onchange with consistent event shape
67
- onchange({ target: { files: input.files } });
68
- }
69
- }
70
-
71
- const fileList = $derived(files ? Array.from(files) : []);
72
- </script>
73
-
74
- <div>
75
- {#if label}
76
- <div class="text-text-soft text-sm mb-2 block">
77
- {label}
78
- </div>
79
- {/if}
80
-
81
- <input
82
- type="file"
83
- {id}
84
- {accept}
85
- {multiple}
86
- {disabled}
87
- bind:this={inputElement}
88
- bind:files
89
- onchange={handleInputChange}
90
- class="hidden"
91
- />
92
-
93
- <div
94
- role="button"
95
- tabindex={disabled ? -1 : 0}
96
- aria-label="Click or drag files to upload"
97
- ondragover={handleDragOver}
98
- ondragleave={handleDragLeave}
99
- ondrop={handleDrop}
100
- onclick={handleClick}
101
- onkeydown={(e) => e.key === 'Enter' && handleClick()}
102
- class="glass-panel rounded-lg p-8 text-center cursor-pointer transition-all duration-300 ease-luxe {isDragging ? 'border-2 border-accent accent-glow bg-base-2' : ''} {disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''}"
103
- >
104
- <div class="flex flex-col items-center gap-2">
105
- <svg
106
- xmlns="http://www.w3.org/2000/svg"
107
- width="48"
108
- height="48"
109
- viewBox="0 0 24 24"
110
- fill="none"
111
- stroke="currentColor"
112
- stroke-width="2"
113
- stroke-linecap="round"
114
- stroke-linejoin="round"
115
- class="text-text-soft"
116
- >
117
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
118
- <polyline points="17 8 12 3 7 8" />
119
- <line x1="12" y1="3" x2="12" y2="15" />
120
- </svg>
121
-
122
- <div>
123
- <p class="text-text text-sm font-medium">
124
- Click to upload or drag and drop
125
- </p>
126
- <p class="text-text-muted text-xs mt-1">
127
- {accept ? `Accepted formats: ${accept}` : 'Any file type'}
128
- </p>
129
- </div>
130
- </div>
131
- </div>
132
-
133
- {#if fileList.length > 0}
134
- <div class="mt-4">
135
- <p class="text-text-soft text-xs mb-2">
136
- Selected files:
137
- </p>
138
- <ul class="space-y-1">
139
- {#each fileList as file}
140
- <li class="text-text text-xs flex items-center gap-2">
141
- <svg
142
- xmlns="http://www.w3.org/2000/svg"
143
- width="12"
144
- height="12"
145
- viewBox="0 0 24 24"
146
- fill="none"
147
- stroke="currentColor"
148
- stroke-width="2"
149
- stroke-linecap="round"
150
- stroke-linejoin="round"
151
- >
152
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
153
- <polyline points="14 2 14 8 20 8" />
154
- </svg>
155
- <span>{file.name}</span>
156
- <span class="text-text-muted">
157
- ({(file.size / 1024).toFixed(1)} KB)
158
- </span>
159
- </li>
160
- {/each}
161
- </ul>
162
- </div>
163
- {/if}
164
- </div>
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ let { files = $bindable(null), accept, multiple = false, disabled = false, label, id = createId('file-upload'), onchange, icon } = $props();
3
+ let isDragging = $state(false);
4
+ let inputElement;
5
+ function handleDragOver(event) {
6
+ event.preventDefault();
7
+ if (!disabled) {
8
+ isDragging = true;
9
+ }
10
+ }
11
+ function handleDragLeave() {
12
+ isDragging = false;
13
+ }
14
+ function handleDrop(event) {
15
+ event.preventDefault();
16
+ isDragging = false;
17
+ if (!disabled && event.dataTransfer?.files) {
18
+ files = event.dataTransfer.files;
19
+ if (onchange) {
20
+ // Call onchange directly with synthetic event object
21
+ onchange({ target: { files: event.dataTransfer.files } });
22
+ }
23
+ }
24
+ }
25
+ function handleClick() {
26
+ if (!disabled) {
27
+ inputElement?.click();
28
+ }
29
+ }
30
+ function handleInputChange(event) {
31
+ const input = event.target;
32
+ if (onchange && input.files) {
33
+ // Call onchange with consistent event shape
34
+ onchange({ target: { files: input.files } });
35
+ }
36
+ }
37
+ const fileList = $derived(files ? Array.from(files) : []);
38
+ </script>
39
+
40
+ <div>
41
+ {#if label}
42
+ <div class="text-text-soft text-sm mb-2 block">
43
+ {label}
44
+ </div>
45
+ {/if}
46
+
47
+ <input
48
+ type="file"
49
+ {id}
50
+ {accept}
51
+ {multiple}
52
+ {disabled}
53
+ bind:this={inputElement}
54
+ bind:files
55
+ onchange={handleInputChange}
56
+ class="hidden"
57
+ />
58
+
59
+ <div
60
+ role="button"
61
+ tabindex={disabled ? -1 : 0}
62
+ aria-label="Click or drag files to upload"
63
+ ondragover={handleDragOver}
64
+ ondragleave={handleDragLeave}
65
+ ondrop={handleDrop}
66
+ onclick={handleClick}
67
+ onkeydown={(e) => e.key === 'Enter' && handleClick()}
68
+ class="panel-raised rounded-[var(--radius-lg)] p-8 text-center cursor-pointer transition-all duration-300 ease-luxe {isDragging ? 'border-2 border-accent accent-glow bg-base-2' : ''} {disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''}"
69
+ >
70
+ <div class="flex flex-col items-center gap-2">
71
+ {#if icon}
72
+ <span class="inline-flex items-center justify-center text-text-soft">
73
+ {@render icon()}
74
+ </span>
75
+ {:else}
76
+ <svg
77
+ xmlns="http://www.w3.org/2000/svg"
78
+ width="48"
79
+ height="48"
80
+ viewBox="0 0 24 24"
81
+ fill="none"
82
+ stroke="currentColor"
83
+ stroke-width="2"
84
+ stroke-linecap="round"
85
+ stroke-linejoin="round"
86
+ class="text-text-soft"
87
+ >
88
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
89
+ <polyline points="17 8 12 3 7 8" />
90
+ <line x1="12" y1="3" x2="12" y2="15" />
91
+ </svg>
92
+ {/if}
93
+
94
+ <div>
95
+ <p class="text-text text-sm font-medium">
96
+ Click to upload or drag and drop
97
+ </p>
98
+ <p class="text-text-muted text-xs mt-1">
99
+ {accept ? `Accepted formats: ${accept}` : 'Any file type'}
100
+ </p>
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ {#if fileList.length > 0}
106
+ <div class="mt-4">
107
+ <p class="text-text-soft text-xs mb-2">
108
+ Selected files:
109
+ </p>
110
+ <ul class="space-y-1">
111
+ {#each fileList as file}
112
+ <li class="text-text text-xs flex items-center gap-2">
113
+ <svg
114
+ xmlns="http://www.w3.org/2000/svg"
115
+ width="12"
116
+ height="12"
117
+ viewBox="0 0 24 24"
118
+ fill="none"
119
+ stroke="currentColor"
120
+ stroke-width="2"
121
+ stroke-linecap="round"
122
+ stroke-linejoin="round"
123
+ >
124
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
125
+ <polyline points="14 2 14 8 20 8" />
126
+ </svg>
127
+ <span>{file.name}</span>
128
+ <span class="text-text-muted">
129
+ ({(file.size / 1024).toFixed(1)} KB)
130
+ </span>
131
+ </li>
132
+ {/each}
133
+ </ul>
134
+ </div>
135
+ {/if}
136
+ </div>
@@ -16,6 +16,7 @@ interface Props {
16
16
  files: FileList;
17
17
  };
18
18
  }) => void;
19
+ icon?: import('svelte').Snippet;
19
20
  }
20
21
  declare const FileUpload: import("svelte").Component<Props, {}, "files">;
21
22
  type FileUpload = ReturnType<typeof FileUpload>;