@mrintel/villain-ui 0.2.2 → 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.
- package/LICENSE +21 -21
- package/README.md +3490 -1296
- package/dist/components/buttons/Button.svelte +27 -0
- package/dist/components/buttons/Button.svelte.d.ts +14 -0
- package/dist/components/buttons/ButtonGroup.svelte +17 -0
- package/dist/components/buttons/ButtonGroup.svelte.d.ts +8 -0
- package/dist/components/buttons/FloatingActionButton.svelte +20 -0
- package/dist/components/buttons/FloatingActionButton.svelte.d.ts +12 -0
- package/dist/components/buttons/IconButton.svelte +23 -0
- package/dist/components/buttons/IconButton.svelte.d.ts +14 -0
- package/dist/components/buttons/LinkButton.svelte +24 -0
- package/dist/components/buttons/LinkButton.svelte.d.ts +15 -0
- package/dist/components/buttons/buttonClasses.d.ts +15 -0
- package/dist/components/buttons/buttonClasses.js +15 -0
- package/dist/components/buttons/index.d.ts +5 -0
- package/dist/components/buttons/index.js +5 -0
- package/dist/components/cards/Card.svelte +60 -0
- package/dist/components/cards/Card.svelte.d.ts +15 -0
- package/dist/components/cards/Container.svelte +17 -0
- package/dist/components/cards/Container.svelte.d.ts +10 -0
- package/dist/components/cards/Divider.svelte +36 -0
- package/dist/components/cards/Divider.svelte.d.ts +11 -0
- package/dist/components/cards/Grid.svelte +55 -0
- package/dist/components/cards/Grid.svelte.d.ts +10 -0
- package/dist/components/cards/Panel.svelte +18 -0
- package/dist/components/cards/Panel.svelte.d.ts +11 -0
- package/dist/components/cards/SectionHeader.svelte +24 -0
- package/dist/components/cards/SectionHeader.svelte.d.ts +12 -0
- package/dist/components/cards/index.d.ts +6 -0
- package/dist/components/cards/index.js +6 -0
- package/dist/components/data/Avatar.svelte +48 -0
- package/dist/components/data/Avatar.svelte.d.ts +10 -0
- package/dist/components/data/Badge.svelte +45 -0
- package/dist/components/data/Badge.svelte.d.ts +14 -0
- package/dist/components/data/CalendarGrid.svelte +433 -0
- package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
- package/dist/components/data/CalendarGrid.types.d.ts +7 -0
- package/dist/components/data/CalendarGrid.types.js +1 -0
- package/dist/components/data/CodeBlock.svelte +119 -0
- package/dist/components/data/CodeBlock.svelte.d.ts +40 -0
- package/dist/components/data/List.svelte +87 -0
- package/dist/components/data/List.svelte.d.ts +15 -0
- package/dist/components/data/Pagination.svelte +121 -0
- package/dist/components/data/Pagination.svelte.d.ts +14 -0
- package/dist/components/data/Sparkline.svelte +117 -0
- package/dist/components/data/Sparkline.svelte.d.ts +43 -0
- package/dist/components/data/Stat.svelte +92 -0
- package/dist/components/data/Stat.svelte.d.ts +11 -0
- package/dist/components/data/Table.svelte +443 -0
- package/dist/components/data/Table.svelte.d.ts +30 -0
- package/dist/components/data/Table.types.d.ts +14 -0
- package/dist/components/data/Table.types.js +1 -0
- package/dist/components/data/Tag.svelte +51 -0
- package/dist/components/data/Tag.svelte.d.ts +13 -0
- package/dist/components/data/index.d.ts +12 -0
- package/dist/components/data/index.js +10 -0
- package/dist/components/forms/Checkbox.svelte +39 -0
- package/dist/components/forms/Checkbox.svelte.d.ts +12 -0
- package/dist/components/forms/DatePicker.svelte +61 -0
- package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
- package/dist/components/forms/DateTimePicker.svelte +63 -0
- package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
- package/dist/components/forms/FileUpload.svelte +136 -0
- package/dist/components/forms/FileUpload.svelte.d.ts +23 -0
- package/dist/components/forms/Input.svelte +282 -0
- package/dist/components/forms/Input.svelte.d.ts +19 -0
- package/dist/components/forms/InputGroup.svelte +7 -0
- package/dist/components/forms/InputGroup.svelte.d.ts +20 -0
- package/dist/components/forms/RadioGroup.svelte +77 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts +17 -0
- package/dist/components/forms/RangeSlider.svelte +90 -0
- package/dist/components/forms/RangeSlider.svelte.d.ts +14 -0
- package/dist/components/forms/Select.svelte +106 -0
- package/dist/components/forms/Select.svelte.d.ts +18 -0
- package/dist/components/forms/Switch.svelte +44 -0
- package/dist/components/forms/Switch.svelte.d.ts +12 -0
- package/dist/components/forms/Textarea.svelte +52 -0
- package/dist/components/forms/Textarea.svelte.d.ts +15 -0
- package/dist/components/forms/TimePicker.svelte +63 -0
- package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
- package/dist/components/forms/formClasses.d.ts +3 -0
- package/dist/components/forms/formClasses.js +3 -0
- package/dist/components/forms/index.d.ts +12 -0
- package/dist/components/forms/index.js +12 -0
- package/dist/components/navigation/Breadcrumbs.svelte +56 -0
- package/dist/components/navigation/Breadcrumbs.svelte.d.ts +15 -0
- package/dist/components/navigation/ContextMenu.svelte +133 -0
- package/dist/components/navigation/ContextMenu.svelte.d.ts +18 -0
- package/dist/components/navigation/DropdownMenu.svelte +139 -0
- package/dist/components/navigation/DropdownMenu.svelte.d.ts +17 -0
- package/dist/components/navigation/Menu.svelte +72 -0
- package/dist/components/navigation/Menu.svelte.d.ts +15 -0
- package/dist/components/navigation/Navbar.svelte +111 -0
- package/dist/components/navigation/Navbar.svelte.d.ts +15 -0
- package/dist/components/navigation/Sidebar.svelte +236 -0
- package/dist/components/navigation/Sidebar.svelte.d.ts +12 -0
- package/dist/components/navigation/Tabs.svelte +86 -0
- package/dist/components/navigation/Tabs.svelte.d.ts +19 -0
- package/dist/components/navigation/index.d.ts +7 -0
- package/dist/components/navigation/index.js +7 -0
- package/dist/components/overlays/Alert.svelte +81 -0
- package/dist/components/overlays/Alert.svelte.d.ts +15 -0
- package/dist/components/overlays/CommandPalette.svelte +182 -0
- package/dist/components/overlays/CommandPalette.svelte.d.ts +16 -0
- package/dist/components/overlays/Drawer.svelte +158 -0
- package/dist/components/overlays/Drawer.svelte.d.ts +16 -0
- package/dist/components/overlays/Dropdown.svelte +62 -0
- package/dist/components/overlays/Dropdown.svelte.d.ts +11 -0
- package/dist/components/overlays/Modal.svelte +125 -0
- package/dist/components/overlays/Modal.svelte.d.ts +15 -0
- package/dist/components/overlays/Popover.svelte +106 -0
- package/dist/components/overlays/Popover.svelte.d.ts +11 -0
- package/dist/components/overlays/ProgressBar.svelte +29 -0
- package/dist/components/overlays/ProgressBar.svelte.d.ts +10 -0
- package/dist/components/overlays/SkeletonLoader.svelte +66 -0
- package/dist/components/overlays/SkeletonLoader.svelte.d.ts +9 -0
- package/dist/components/overlays/Spinner.svelte +33 -0
- package/dist/components/overlays/Spinner.svelte.d.ts +7 -0
- package/dist/components/overlays/Toast.svelte +111 -0
- package/dist/components/overlays/Toast.svelte.d.ts +16 -0
- package/dist/components/overlays/Tooltip.svelte +94 -0
- package/dist/components/overlays/Tooltip.svelte.d.ts +12 -0
- package/dist/components/overlays/index.d.ts +11 -0
- package/dist/components/overlays/index.js +11 -0
- package/dist/components/typography/Code.svelte +10 -0
- package/dist/components/typography/Code.svelte.d.ts +6 -0
- package/dist/components/typography/Heading.svelte +15 -0
- package/dist/components/typography/Heading.svelte.d.ts +10 -0
- package/dist/components/typography/Text.svelte +21 -0
- package/dist/components/typography/Text.svelte.d.ts +10 -0
- package/dist/components/typography/index.d.ts +3 -0
- package/dist/components/typography/index.js +3 -0
- package/dist/components/utilities/Accordion.svelte +54 -0
- package/dist/components/utilities/Accordion.svelte.d.ts +17 -0
- package/dist/components/utilities/Carousel.svelte +124 -0
- package/dist/components/utilities/Carousel.svelte.d.ts +16 -0
- package/dist/components/utilities/Collapse.svelte +46 -0
- package/dist/components/utilities/Collapse.svelte.d.ts +10 -0
- package/dist/components/utilities/Hero.svelte +42 -0
- package/dist/components/utilities/Hero.svelte.d.ts +10 -0
- package/dist/components/utilities/Portal.svelte +47 -0
- package/dist/components/utilities/Portal.svelte.d.ts +21 -0
- package/dist/components/utilities/ScrollArea.svelte +33 -0
- package/dist/components/utilities/ScrollArea.svelte.d.ts +8 -0
- package/dist/components/utilities/SystemConsole.svelte +310 -0
- package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
- package/dist/components/utilities/SystemInterface.svelte +726 -0
- package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
- package/dist/components/utilities/index.d.ts +9 -0
- package/dist/components/utilities/index.js +8 -0
- package/dist/components/utilities/utilities.types.d.ts +46 -0
- package/dist/components/utilities/utilities.types.js +4 -0
- package/dist/index.d.ts +60 -175
- package/dist/index.js +24 -4560
- package/dist/lib/internal/id.d.ts +12 -0
- package/dist/lib/internal/id.js +15 -0
- package/dist/theme.css +2821 -0
- package/package.json +83 -75
- package/dist/index.css +0 -1
|
@@ -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;
|
|
@@ -0,0 +1,136 @@
|
|
|
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>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileUpload component props.
|
|
3
|
+
*
|
|
4
|
+
* The `onchange` callback receives an event object with the following shape:
|
|
5
|
+
* `{ target: { files: FileList } }` for both drag-and-drop and click-based selection.
|
|
6
|
+
*/
|
|
7
|
+
interface Props {
|
|
8
|
+
files?: FileList | null;
|
|
9
|
+
accept?: string;
|
|
10
|
+
multiple?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
label?: string;
|
|
13
|
+
id?: string;
|
|
14
|
+
onchange?: (event: {
|
|
15
|
+
target: {
|
|
16
|
+
files: FileList;
|
|
17
|
+
};
|
|
18
|
+
}) => void;
|
|
19
|
+
icon?: import('svelte').Snippet;
|
|
20
|
+
}
|
|
21
|
+
declare const FileUpload: import("svelte").Component<Props, {}, "files">;
|
|
22
|
+
type FileUpload = ReturnType<typeof FileUpload>;
|
|
23
|
+
export default FileUpload;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
|
+
import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
|
|
3
|
+
let { type = 'text', value = $bindable(''), placeholder, disabled = false, error = false, label, id = createId('input'), oninput, iconBefore, iconAfter, validate, validationMessage, showValidation = true, class: className = '', } = $props();
|
|
4
|
+
// Built-in validation patterns
|
|
5
|
+
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
6
|
+
const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
|
|
7
|
+
const telPattern = /^[\d\s\-\+\(\)]+$/;
|
|
8
|
+
// Validation logic
|
|
9
|
+
let validationError = $state(null);
|
|
10
|
+
function performValidation(val) {
|
|
11
|
+
if (!val || val === '') {
|
|
12
|
+
validationError = null;
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
// Custom validation function
|
|
16
|
+
if (validate) {
|
|
17
|
+
const result = validate(val);
|
|
18
|
+
if (result === true) {
|
|
19
|
+
validationError = null;
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
else if (typeof result === 'string') {
|
|
23
|
+
validationError = result;
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
validationError = validationMessage || 'Invalid input';
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Built-in validation based on type
|
|
32
|
+
const stringVal = String(val);
|
|
33
|
+
switch (type) {
|
|
34
|
+
case 'email':
|
|
35
|
+
if (!emailPattern.test(stringVal)) {
|
|
36
|
+
validationError = validationMessage || 'Please enter a valid email address';
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
case 'url':
|
|
41
|
+
if (!urlPattern.test(stringVal)) {
|
|
42
|
+
validationError = validationMessage || 'Please enter a valid URL';
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
case 'tel':
|
|
47
|
+
if (!telPattern.test(stringVal)) {
|
|
48
|
+
validationError = validationMessage || 'Please enter a valid phone number';
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
validationError = null;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// Validate on value change
|
|
57
|
+
$effect(() => {
|
|
58
|
+
if (value !== undefined && value !== '') {
|
|
59
|
+
performValidation(value);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const hasError = $derived(error || (validationError !== null));
|
|
63
|
+
const errorClasses = $derived(hasError ? 'error-state' : '');
|
|
64
|
+
function increment() {
|
|
65
|
+
if (disabled || type !== 'number')
|
|
66
|
+
return;
|
|
67
|
+
value = Number(value || 0) + 1;
|
|
68
|
+
}
|
|
69
|
+
function decrement() {
|
|
70
|
+
if (disabled || type !== 'number')
|
|
71
|
+
return;
|
|
72
|
+
value = Number(value || 0) - 1;
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{#if label}
|
|
77
|
+
<div>
|
|
78
|
+
<label for={id} class="text-text-soft text-sm mb-2 block">
|
|
79
|
+
{label}
|
|
80
|
+
</label>
|
|
81
|
+
<div class="relative flex items-center">
|
|
82
|
+
<!-- BEFORE ICON -->
|
|
83
|
+
{#if iconBefore}
|
|
84
|
+
<span
|
|
85
|
+
class="absolute left-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
|
|
86
|
+
>
|
|
87
|
+
{@render iconBefore()}
|
|
88
|
+
</span>
|
|
89
|
+
{/if}
|
|
90
|
+
|
|
91
|
+
<!-- INPUT FIELD -->
|
|
92
|
+
<input
|
|
93
|
+
{type}
|
|
94
|
+
{id}
|
|
95
|
+
{placeholder}
|
|
96
|
+
{disabled}
|
|
97
|
+
bind:value
|
|
98
|
+
{oninput}
|
|
99
|
+
class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
|
|
100
|
+
? disabledClasses
|
|
101
|
+
: ''} {className}"
|
|
102
|
+
class:pl-12={iconBefore}
|
|
103
|
+
class:pr-12={iconAfter || type === 'number'}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<!-- AFTER ICON (non-number) -->
|
|
107
|
+
{#if iconAfter && type !== 'number'}
|
|
108
|
+
<span
|
|
109
|
+
class="absolute right-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
|
|
110
|
+
>
|
|
111
|
+
{@render iconAfter()}
|
|
112
|
+
</span>
|
|
113
|
+
{/if}
|
|
114
|
+
|
|
115
|
+
<!-- CUSTOM ARROWS (NUMBER INPUT ONLY) -->
|
|
116
|
+
{#if type === 'number'}
|
|
117
|
+
<div class="number-arrows">
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
onclick={increment}
|
|
121
|
+
class="arrow-btn arrow-up"
|
|
122
|
+
tabindex="-1"
|
|
123
|
+
>
|
|
124
|
+
▲
|
|
125
|
+
</button>
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
onclick={decrement}
|
|
129
|
+
class="arrow-btn arrow-down"
|
|
130
|
+
tabindex="-1"
|
|
131
|
+
>
|
|
132
|
+
▼
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
{/if}
|
|
136
|
+
</div>
|
|
137
|
+
{#if showValidation && validationError}
|
|
138
|
+
<p class="text-error text-xs mt-1.5">
|
|
139
|
+
{validationError}
|
|
140
|
+
</p>
|
|
141
|
+
{/if}
|
|
142
|
+
</div>
|
|
143
|
+
{:else}
|
|
144
|
+
<div>
|
|
145
|
+
<div class="relative flex items-center">
|
|
146
|
+
{#if iconBefore}
|
|
147
|
+
<span
|
|
148
|
+
class="absolute left-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
|
|
149
|
+
>
|
|
150
|
+
{@render iconBefore()}
|
|
151
|
+
</span>
|
|
152
|
+
{/if}
|
|
153
|
+
|
|
154
|
+
<input
|
|
155
|
+
{type}
|
|
156
|
+
{id}
|
|
157
|
+
{placeholder}
|
|
158
|
+
{disabled}
|
|
159
|
+
bind:value
|
|
160
|
+
{oninput}
|
|
161
|
+
class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
|
|
162
|
+
? disabledClasses
|
|
163
|
+
: ''} {className}"
|
|
164
|
+
class:pl-12={iconBefore}
|
|
165
|
+
class:pr-12={iconAfter || type === 'number'}
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
{#if iconAfter && type !== 'number'}
|
|
169
|
+
<span
|
|
170
|
+
class="absolute right-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
|
|
171
|
+
>
|
|
172
|
+
{@render iconAfter()}
|
|
173
|
+
</span>
|
|
174
|
+
{/if}
|
|
175
|
+
|
|
176
|
+
{#if type === 'number'}
|
|
177
|
+
<div class="number-arrows">
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
onclick={increment}
|
|
181
|
+
class="arrow-btn arrow-up"
|
|
182
|
+
tabindex="-1"
|
|
183
|
+
>
|
|
184
|
+
▲
|
|
185
|
+
</button>
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
onclick={decrement}
|
|
189
|
+
class="arrow-btn arrow-down"
|
|
190
|
+
tabindex="-1"
|
|
191
|
+
>
|
|
192
|
+
▼
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
{/if}
|
|
196
|
+
</div>
|
|
197
|
+
{#if showValidation && validationError}
|
|
198
|
+
<p class="text-error text-xs mt-1.5">
|
|
199
|
+
{validationError}
|
|
200
|
+
</p>
|
|
201
|
+
{/if}
|
|
202
|
+
</div>
|
|
203
|
+
{/if}
|
|
204
|
+
|
|
205
|
+
<style>
|
|
206
|
+
.number-arrows {
|
|
207
|
+
position: absolute;
|
|
208
|
+
top: 0;
|
|
209
|
+
right: 0;
|
|
210
|
+
height: 100%;
|
|
211
|
+
width: 2.25rem; /* doesn't overlap your icons */
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
border-left: 1px solid var(--color-border-strong);
|
|
215
|
+
background: color-mix(in srgb, var(--color-accent) 8%, transparent);
|
|
216
|
+
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
|
217
|
+
overflow: hidden;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.arrow-btn {
|
|
221
|
+
flex: 1;
|
|
222
|
+
display: flex;
|
|
223
|
+
align-items: center;
|
|
224
|
+
justify-content: center;
|
|
225
|
+
background: transparent;
|
|
226
|
+
color: var(--color-text-soft);
|
|
227
|
+
font-size: 0.7rem;
|
|
228
|
+
cursor: pointer;
|
|
229
|
+
transition: all 120ms var(--ease-sharp);
|
|
230
|
+
user-select: none;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.arrow-btn:hover {
|
|
234
|
+
background: var(--color-accent-overlay-20);
|
|
235
|
+
color: var(--color-accent-soft);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.arrow-btn:active {
|
|
239
|
+
background: var(--color-accent-overlay-30);
|
|
240
|
+
color: var(--color-accent);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.arrow-btn:disabled,
|
|
244
|
+
.arrow-btn[disabled] {
|
|
245
|
+
opacity: 0.4;
|
|
246
|
+
cursor: not-allowed;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Remove default browser arrows */
|
|
250
|
+
input[type='number']::-webkit-inner-spin-button,
|
|
251
|
+
input[type='number']::-webkit-outer-spin-button {
|
|
252
|
+
-webkit-appearance: none;
|
|
253
|
+
margin: 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
input[type='number'] {
|
|
257
|
+
-moz-appearance: textfield;
|
|
258
|
+
appearance: textfield;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Color input styling */
|
|
262
|
+
input[type='color'] {
|
|
263
|
+
width: 60px;
|
|
264
|
+
min-width: 60px;
|
|
265
|
+
min-height: 3rem;
|
|
266
|
+
padding: 0.375rem;
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
input[type='color']::-webkit-color-swatch-wrapper {
|
|
271
|
+
padding: 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
input[type='color']::-webkit-color-swatch {
|
|
275
|
+
border: 1px solid var(--color-border-strong);
|
|
276
|
+
border-radius: calc(var(--radius-md) - 2px);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
input[type='color']::-moz-color-swatch {
|
|
280
|
+
border: 1px solid var(--color-border-strong);
|
|
281
|
+
border-radius: calc(var(--radius-md) - 2px);
|
|
282
|
+
}</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface Props {
|
|
2
|
+
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'color' | 'search';
|
|
3
|
+
value?: string | number;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
error?: boolean;
|
|
7
|
+
label?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
oninput?: (event: Event) => void;
|
|
10
|
+
iconBefore?: import('svelte').Snippet;
|
|
11
|
+
iconAfter?: import('svelte').Snippet;
|
|
12
|
+
validate?: (value: string | number) => boolean | string;
|
|
13
|
+
validationMessage?: string;
|
|
14
|
+
showValidation?: boolean;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
declare const Input: import("svelte").Component<Props, {}, "value">;
|
|
18
|
+
type Input = ReturnType<typeof Input>;
|
|
19
|
+
export default Input;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<script lang="ts">"use strict";
|
|
2
|
+
// No props needed for standard slot usage
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="flex items-stretch rounded-[var(--radius-lg)] overflow-hidden panel-raised [&>*:not(:first-child)]:ml-[-1px] [&>*:not(:first-child):not(:last-child)]:rounded-none [&>*:first-child]:rounded-r-none [&>*:last-child]:rounded-l-none">
|
|
6
|
+
<slot />
|
|
7
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const InputGroup: $$__sveltets_2_IsomorphicComponent<any, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {
|
|
17
|
+
default: {};
|
|
18
|
+
}, {}, string>;
|
|
19
|
+
type InputGroup = InstanceType<typeof InputGroup>;
|
|
20
|
+
export default InputGroup;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts">let { value = $bindable(''), options, name, disabled = false, orientation = 'vertical', label, onchange, class: className = '' } = $props();
|
|
2
|
+
const containerClasses = $derived(orientation === 'vertical' ? 'flex flex-col gap-3' : 'flex flex-row gap-4');
|
|
3
|
+
export {};
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
{#if label}
|
|
7
|
+
<fieldset class="{disabled ? 'opacity-50' : ''} {className}">
|
|
8
|
+
<legend class="text-[var(--color-text-soft)] text-sm mb-3 block">
|
|
9
|
+
{label}
|
|
10
|
+
</legend>
|
|
11
|
+
<div class={containerClasses}>
|
|
12
|
+
{#each options as option}
|
|
13
|
+
{@const radioId = `${name}-${option.value}`}
|
|
14
|
+
<label for={radioId} class="flex items-center gap-2 cursor-pointer {disabled ? 'cursor-not-allowed' : ''}">
|
|
15
|
+
<input
|
|
16
|
+
type="radio"
|
|
17
|
+
id={radioId}
|
|
18
|
+
{name}
|
|
19
|
+
value={option.value}
|
|
20
|
+
{disabled}
|
|
21
|
+
bind:group={value}
|
|
22
|
+
onchange={onchange}
|
|
23
|
+
class="w-6 h-6 rounded-[var(--radius-pill)] border-2 border-[var(--color-border-strong)] bg-transparent appearance-none transition-all duration-200 ease-[var(--ease-luxe)] cursor-pointer checked:border-[var(--color-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:ring-offset-2 focus:ring-offset-[var(--color-base-1)] relative {disabled ? 'cursor-not-allowed' : ''}"
|
|
24
|
+
/>
|
|
25
|
+
{#if option.iconBefore}
|
|
26
|
+
<span class="inline-flex items-center justify-center text-text-soft">
|
|
27
|
+
{@render option.iconBefore()}
|
|
28
|
+
</span>
|
|
29
|
+
{/if}
|
|
30
|
+
<span class="text-[var(--color-text)] text-sm select-none">
|
|
31
|
+
{option.label}
|
|
32
|
+
</span>
|
|
33
|
+
</label>
|
|
34
|
+
{/each}
|
|
35
|
+
</div>
|
|
36
|
+
</fieldset>
|
|
37
|
+
{:else}
|
|
38
|
+
<div class="{containerClasses} {className}">
|
|
39
|
+
{#each options as option}
|
|
40
|
+
{@const radioId = `${name}-${option.value}`}
|
|
41
|
+
<label for={radioId} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''}">
|
|
42
|
+
<input
|
|
43
|
+
type="radio"
|
|
44
|
+
id={radioId}
|
|
45
|
+
{name}
|
|
46
|
+
value={option.value}
|
|
47
|
+
{disabled}
|
|
48
|
+
bind:group={value}
|
|
49
|
+
onchange={onchange}
|
|
50
|
+
class="w-6 h-6 rounded-[var(--radius-pill)] border-2 border-[var(--color-border-strong)] bg-transparent appearance-none transition-all duration-200 ease-[var(--ease-luxe)] cursor-pointer checked:border-[var(--color-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:ring-offset-2 focus:ring-offset-[var(--color-base-1)] relative {disabled ? 'cursor-not-allowed' : ''}"
|
|
51
|
+
/>
|
|
52
|
+
{#if option.iconBefore}
|
|
53
|
+
<span class="inline-flex items-center justify-center text-text-soft">
|
|
54
|
+
{@render option.iconBefore()}
|
|
55
|
+
</span>
|
|
56
|
+
{/if}
|
|
57
|
+
<span class="text-[var(--color-text)] text-sm select-none">
|
|
58
|
+
{option.label}
|
|
59
|
+
</span>
|
|
60
|
+
</label>
|
|
61
|
+
{/each}
|
|
62
|
+
</div>
|
|
63
|
+
{/if}
|
|
64
|
+
|
|
65
|
+
<style>
|
|
66
|
+
input[type="radio"]:checked::after {
|
|
67
|
+
content: '';
|
|
68
|
+
position: absolute;
|
|
69
|
+
left: 50%;
|
|
70
|
+
top: 50%;
|
|
71
|
+
transform: translate(-50%, -50%);
|
|
72
|
+
width: 0.75rem;
|
|
73
|
+
height: 0.75rem;
|
|
74
|
+
border-radius: var(--radius-pill);
|
|
75
|
+
background: var(--color-accent);
|
|
76
|
+
box-shadow: var(--shadow-accent-glow);
|
|
77
|
+
}</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Props {
|
|
2
|
+
value?: string;
|
|
3
|
+
options: Array<{
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
iconBefore?: import('svelte').Snippet;
|
|
7
|
+
}>;
|
|
8
|
+
name: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
orientation?: 'vertical' | 'horizontal';
|
|
11
|
+
label?: string;
|
|
12
|
+
onchange?: (event: Event) => void;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
declare const RadioGroup: import("svelte").Component<Props, {}, "value">;
|
|
16
|
+
type RadioGroup = ReturnType<typeof RadioGroup>;
|
|
17
|
+
export default RadioGroup;
|