negodesign 0.0.74 → 0.0.76
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/dist/components/pages/security/auth/PageAuth01.svelte +65 -0
- package/dist/components/pages/security/auth/PageAuth01.svelte.d.ts +17 -0
- package/dist/components/pages/security/auth/PageAuth02.svelte +45 -0
- package/dist/components/pages/security/auth/PageAuth02.svelte.d.ts +17 -0
- package/dist/components/pages/security/auth/PageAuth03.svelte +45 -0
- package/dist/components/pages/security/auth/PageAuth03.svelte.d.ts +17 -0
- package/dist/components/pages/security/login/01/ui/PageLogin01.svelte +15 -40
- package/dist/components/pages/security/login/02/ui/LoginCard02.svelte +15 -4
- package/dist/components/pages/security/login/02/ui/PageLogin02.svelte +15 -20
- package/dist/components/pages/security/login/03/ui/LoginCard03.svelte +34 -16
- package/dist/components/pages/security/login/03/ui/PageLogin03.svelte +15 -20
- package/dist/components/pages/security/register/01/ui/PageRegister01.svelte +64 -0
- package/dist/components/pages/security/register/01/ui/PageRegister01.svelte.d.ts +3 -0
- package/dist/components/pages/security/register/01/ui/RegisterCard.svelte +100 -0
- package/dist/components/pages/security/register/01/ui/RegisterCard.svelte.d.ts +12 -0
- package/dist/components/pages/security/register/02/ui/PageRegister02.svelte +76 -0
- package/dist/components/pages/security/register/02/ui/PageRegister02.svelte.d.ts +4 -0
- package/dist/components/pages/security/register/02/ui/RegisterCard02.svelte +98 -0
- package/dist/components/pages/security/register/02/ui/RegisterCard02.svelte.d.ts +12 -0
- package/dist/components/pages/security/register/03/ui/PageRegister03.svelte +76 -0
- package/dist/components/pages/security/register/03/ui/PageRegister03.svelte.d.ts +4 -0
- package/dist/components/pages/security/register/03/ui/RegisterCard03.svelte +103 -0
- package/dist/components/pages/security/register/03/ui/RegisterCard03.svelte.d.ts +12 -0
- package/dist/components/pages/security/register/FormRegister.svelte +120 -0
- package/dist/components/pages/security/register/FormRegister.svelte.d.ts +8 -0
- package/dist/components/pages/security/register/PageRegister.svelte +30 -0
- package/dist/components/pages/security/register/PageRegister.svelte.d.ts +8 -0
- package/dist/components/pages/security/register/types.d.ts +51 -0
- package/dist/components/pages/security/register/types.js +1 -0
- package/dist/components/ui/form/Form.svelte +171 -0
- package/dist/components/ui/form/Form.svelte.d.ts +4 -0
- package/dist/components/ui/form/types.d.ts +78 -0
- package/dist/components/ui/form/types.js +1 -0
- package/dist/components/ui/form/ui/input-badges.svelte +214 -0
- package/dist/components/ui/form/ui/input-badges.svelte.d.ts +14 -0
- package/dist/components/ui/form/ui/input-checkbox.svelte +86 -0
- package/dist/components/ui/form/ui/input-checkbox.svelte.d.ts +17 -0
- package/dist/components/ui/form/ui/input-radio.svelte +82 -0
- package/dist/components/ui/form/ui/input-radio.svelte.d.ts +17 -0
- package/dist/components/ui/form/ui/input-select.svelte +195 -0
- package/dist/components/ui/form/ui/input-select.svelte.d.ts +17 -0
- package/dist/components/ui/form/ui/input-textarea.svelte +61 -0
- package/dist/components/ui/form/ui/input-textarea.svelte.d.ts +13 -0
- package/dist/components/ui/form/ui/input-toggle.svelte +89 -0
- package/dist/components/ui/form/ui/input-toggle.svelte.d.ts +11 -0
- package/dist/components/ui/modal/badge/index.d.ts +3 -0
- package/dist/components/ui/modal/badge/index.js +2 -0
- package/dist/components/ui/modal/badge/types.d.ts +35 -0
- package/dist/components/ui/modal/badge/types.js +1 -0
- package/dist/components/ui/modal/badge/ui/ModalBadgeSelection.svelte +87 -0
- package/dist/components/ui/modal/badge/ui/ModalBadgeSelection.svelte.d.ts +4 -0
- package/dist/components/ui/modal/core/index.d.ts +3 -0
- package/dist/components/ui/modal/core/index.js +2 -0
- package/dist/components/ui/modal/core/types.d.ts +84 -0
- package/dist/components/ui/modal/core/types.js +1 -0
- package/dist/components/ui/modal/core/ui/ModalCore.svelte +132 -0
- package/dist/components/ui/modal/core/ui/ModalCore.svelte.d.ts +4 -0
- package/dist/components/ui/modal/feedback/index.d.ts +3 -0
- package/dist/components/ui/modal/feedback/index.js +2 -0
- package/dist/components/ui/modal/feedback/types.d.ts +32 -0
- package/dist/components/ui/modal/feedback/types.js +1 -0
- package/dist/components/ui/modal/feedback/ui/ModalFeedback.svelte +135 -0
- package/dist/components/ui/modal/feedback/ui/ModalFeedback.svelte.d.ts +4 -0
- package/dist/components/ui/modal/form/index.d.ts +3 -0
- package/dist/components/ui/modal/form/index.js +2 -0
- package/dist/components/ui/modal/form/types.d.ts +38 -0
- package/dist/components/ui/modal/form/types.js +1 -0
- package/dist/components/ui/modal/form/ui/ModalForm.svelte +67 -0
- package/dist/components/ui/modal/form/ui/ModalForm.svelte.d.ts +4 -0
- package/dist/components/ui/modal/grid/index.d.ts +5 -0
- package/dist/components/ui/modal/grid/index.js +4 -0
- package/dist/components/ui/modal/grid/types.d.ts +51 -0
- package/dist/components/ui/modal/grid/types.js +1 -0
- package/dist/components/ui/modal/grid/ui/01/ModalGridSelection01.svelte +83 -0
- package/dist/components/ui/modal/grid/ui/01/ModalGridSelection01.svelte.d.ts +4 -0
- package/dist/components/ui/modal/grid/ui/02/ModalGridSelection02.svelte +146 -0
- package/dist/components/ui/modal/grid/ui/02/ModalGridSelection02.svelte.d.ts +4 -0
- package/dist/components/ui/modal/grid/ui/ModalGridSelection.svelte +60 -0
- package/dist/components/ui/modal/grid/ui/ModalGridSelection.svelte.d.ts +4 -0
- package/dist/components/ui/modal/map/index.d.ts +3 -0
- package/dist/components/ui/modal/map/index.js +2 -0
- package/dist/components/ui/modal/map/types.d.ts +48 -0
- package/dist/components/ui/modal/map/types.js +1 -0
- package/dist/components/ui/modal/map/ui/ModalMap.svelte +187 -0
- package/dist/components/ui/modal/map/ui/ModalMap.svelte.d.ts +4 -0
- package/dist/components/ui/modal/notification/index.d.ts +3 -0
- package/dist/components/ui/modal/notification/index.js +2 -0
- package/dist/components/ui/modal/notification/types.d.ts +26 -0
- package/dist/components/ui/modal/notification/types.js +1 -0
- package/dist/components/ui/modal/notification/ui/ModalNotification.svelte +65 -0
- package/dist/components/ui/modal/notification/ui/ModalNotification.svelte.d.ts +4 -0
- package/dist/components/ui/modal/share/index.d.ts +3 -0
- package/dist/components/ui/modal/share/index.js +2 -0
- package/dist/components/ui/modal/share/types.d.ts +47 -0
- package/dist/components/ui/modal/share/types.js +1 -0
- package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte +156 -0
- package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte.d.ts +4 -0
- package/dist/components/ui/modal/upload/index.d.ts +3 -0
- package/dist/components/ui/modal/upload/index.js +2 -0
- package/dist/components/ui/modal/upload/types.d.ts +79 -0
- package/dist/components/ui/modal/upload/types.js +1 -0
- package/dist/components/ui/modal/upload/ui/ModalUpload.svelte +268 -0
- package/dist/components/ui/modal/upload/ui/ModalUpload.svelte.d.ts +4 -0
- package/dist/components/ui/section/Section01.svelte +90 -0
- package/dist/components/ui/section/Section01.svelte.d.ts +4 -0
- package/dist/components/ui/section/Section02.svelte +68 -0
- package/dist/components/ui/section/Section02.svelte.d.ts +4 -0
- package/dist/components/ui/section/Section03.svelte +52 -0
- package/dist/components/ui/section/Section03.svelte.d.ts +4 -0
- package/dist/components/ui/section/index.d.ts +4 -0
- package/dist/components/ui/section/index.js +3 -0
- package/dist/components/ui/section/types.d.ts +46 -0
- package/dist/components/ui/section/types.js +1 -0
- package/dist/docs/DocsSidebar.svelte +58 -1
- package/dist/docs/components.d.ts +1 -1
- package/dist/docs/components.js +968 -0
- package/dist/globals.css +360 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +185 -0
- package/dist/types/index.d.ts +53 -0
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from "../../../utils";
|
|
3
|
+
import InputBase from "./ui/input-base.svelte";
|
|
4
|
+
import InputSelect from "./ui/input-select.svelte";
|
|
5
|
+
import InputRadio from "./ui/input-radio.svelte";
|
|
6
|
+
import InputCheckbox from "./ui/input-checkbox.svelte";
|
|
7
|
+
import InputTextarea from "./ui/input-textarea.svelte";
|
|
8
|
+
import InputToggle from "./ui/input-toggle.svelte";
|
|
9
|
+
import InputBadges from "./ui/input-badges.svelte";
|
|
10
|
+
import type { FormProps, FormInputConfig } from "./types";
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
inputs = [],
|
|
14
|
+
onSubmit,
|
|
15
|
+
submitText = "Submit",
|
|
16
|
+
submitLoadingText = "Submitting...",
|
|
17
|
+
isLoading = false,
|
|
18
|
+
columns = 2,
|
|
19
|
+
gap = "gap-4",
|
|
20
|
+
class: className,
|
|
21
|
+
}: FormProps = $props();
|
|
22
|
+
|
|
23
|
+
let formValues = $state<Record<string, string | string[] | boolean>>(getInitialValues());
|
|
24
|
+
|
|
25
|
+
function getInitialValues(): Record<string, string | string[] | boolean> {
|
|
26
|
+
const values: Record<string, string | string[] | boolean> = {};
|
|
27
|
+
for (const input of inputs) {
|
|
28
|
+
if (input.type === "toggle") {
|
|
29
|
+
values[input.name] = (input.value as boolean) ?? false;
|
|
30
|
+
} else if (input.type === "checkbox" || input.type === "badges" || input.multiple) {
|
|
31
|
+
values[input.name] = (input.value as string[]) ?? [];
|
|
32
|
+
} else {
|
|
33
|
+
values[input.name] = (input.value as string) ?? "";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return values;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function handleTextChange(name: string, e: Event) {
|
|
40
|
+
const val = (e.target as HTMLInputElement | HTMLTextAreaElement).value;
|
|
41
|
+
formValues[name] = val;
|
|
42
|
+
const config = inputs.find((i) => i.name === name);
|
|
43
|
+
if (config?.onChange) {
|
|
44
|
+
(config.onChange as (v: string | string[] | boolean) => void)(val);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleSelectChange(name: string, val: string | string[]) {
|
|
49
|
+
formValues[name] = val;
|
|
50
|
+
const config = inputs.find((i) => i.name === name);
|
|
51
|
+
if (config?.onChange) {
|
|
52
|
+
(config.onChange as (v: string | string[] | boolean) => void)(val);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handleToggleChange(name: string, val: boolean) {
|
|
57
|
+
formValues[name] = val;
|
|
58
|
+
const config = inputs.find((i) => i.name === name);
|
|
59
|
+
if (config?.onChange) {
|
|
60
|
+
(config.onChange as (v: string | string[] | boolean) => void)(val);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleSubmit(e: Event) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
if (isLoading) return;
|
|
67
|
+
onSubmit?.(formValues);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getColSpan(input: FormInputConfig): string {
|
|
71
|
+
if (input.fill) return "col-span-1 md:col-span-2";
|
|
72
|
+
return "col-span-1";
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<form
|
|
77
|
+
onsubmit={handleSubmit}
|
|
78
|
+
class={cn(
|
|
79
|
+
"grid w-full",
|
|
80
|
+
columns === 2 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1",
|
|
81
|
+
gap,
|
|
82
|
+
className
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{#each inputs as input (input.name)}
|
|
86
|
+
<div class={getColSpan(input)}>
|
|
87
|
+
{#if input.type === "select"}
|
|
88
|
+
<InputSelect
|
|
89
|
+
label={input.label}
|
|
90
|
+
isLabel
|
|
91
|
+
placeholder={input.placeholder}
|
|
92
|
+
options={input.options ?? []}
|
|
93
|
+
multiple={input.multiple ?? false}
|
|
94
|
+
disabled={input.disabled ?? false}
|
|
95
|
+
required={input.required ?? false}
|
|
96
|
+
bind:value={() => formValues[input.name] as string, (v) => handleSelectChange(input.name, v)}
|
|
97
|
+
/>
|
|
98
|
+
{:else if input.type === "radio"}
|
|
99
|
+
<InputRadio
|
|
100
|
+
label={input.label}
|
|
101
|
+
isLabel
|
|
102
|
+
name={input.name}
|
|
103
|
+
options={input.options ?? []}
|
|
104
|
+
disabled={input.disabled ?? false}
|
|
105
|
+
required={input.required ?? false}
|
|
106
|
+
bind:value={() => formValues[input.name] as string, (v) => handleSelectChange(input.name, v)}
|
|
107
|
+
/>
|
|
108
|
+
{:else if input.type === "checkbox"}
|
|
109
|
+
<InputCheckbox
|
|
110
|
+
label={input.label}
|
|
111
|
+
isLabel
|
|
112
|
+
name={input.name}
|
|
113
|
+
options={input.options ?? []}
|
|
114
|
+
disabled={input.disabled ?? false}
|
|
115
|
+
required={input.required ?? false}
|
|
116
|
+
bind:value={() => formValues[input.name] as string[], (v) => handleSelectChange(input.name, v)}
|
|
117
|
+
/>
|
|
118
|
+
{:else if input.type === "textarea"}
|
|
119
|
+
<InputTextarea
|
|
120
|
+
label={input.label}
|
|
121
|
+
isLabel
|
|
122
|
+
placeholder={input.placeholder}
|
|
123
|
+
disabled={input.disabled ?? false}
|
|
124
|
+
required={input.required ?? false}
|
|
125
|
+
rows={input.rows ?? 4}
|
|
126
|
+
bind:value={() => formValues[input.name] as string, (v) => handleSelectChange(input.name, v)}
|
|
127
|
+
/>
|
|
128
|
+
{:else if input.type === "toggle"}
|
|
129
|
+
<InputToggle
|
|
130
|
+
label={input.label}
|
|
131
|
+
isLabel
|
|
132
|
+
description={input.description}
|
|
133
|
+
disabled={input.disabled ?? false}
|
|
134
|
+
bind:value={() => formValues[input.name] as boolean, (v) => handleToggleChange(input.name, v)}
|
|
135
|
+
/>
|
|
136
|
+
{:else if input.type === "badges"}
|
|
137
|
+
<InputBadges
|
|
138
|
+
label={input.label}
|
|
139
|
+
isLabel
|
|
140
|
+
placeholder={input.placeholder}
|
|
141
|
+
suggestions={input.suggestions ?? []}
|
|
142
|
+
maxTags={input.maxTags ?? 0}
|
|
143
|
+
disabled={input.disabled ?? false}
|
|
144
|
+
required={input.required ?? false}
|
|
145
|
+
bind:value={() => formValues[input.name] as string[], (v) => handleSelectChange(input.name, v)}
|
|
146
|
+
/>
|
|
147
|
+
{:else}
|
|
148
|
+
<InputBase
|
|
149
|
+
type={input.type === "phone" ? "tel" : input.type}
|
|
150
|
+
label={input.label}
|
|
151
|
+
isLabel
|
|
152
|
+
isIcon
|
|
153
|
+
placeholder={input.placeholder}
|
|
154
|
+
disabled={input.disabled ?? false}
|
|
155
|
+
required={input.required ?? false}
|
|
156
|
+
bind:value={() => formValues[input.name] as string, (v) => handleSelectChange(input.name, v)}
|
|
157
|
+
/>
|
|
158
|
+
{/if}
|
|
159
|
+
</div>
|
|
160
|
+
{/each}
|
|
161
|
+
|
|
162
|
+
<div class={cn("flex justify-end", columns === 2 ? "col-span-1 md:col-span-2" : "col-span-1")}>
|
|
163
|
+
<button
|
|
164
|
+
type="submit"
|
|
165
|
+
disabled={isLoading}
|
|
166
|
+
class="inline-flex items-center gap-2 rounded-lg bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
|
|
167
|
+
>
|
|
168
|
+
{isLoading ? submitLoadingText : submitText}
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
</form>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opção para inputs do tipo select, radio ou checkbox.
|
|
3
|
+
* @property value - Valor identificador da opção.
|
|
4
|
+
* @property label - Texto visível da opção.
|
|
5
|
+
*/
|
|
6
|
+
export type FormOption = {
|
|
7
|
+
value: string;
|
|
8
|
+
label: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Configuração de um input dentro do Form.
|
|
12
|
+
* Cada item define um campo do formulário com o seu tipo e propriedades.
|
|
13
|
+
*
|
|
14
|
+
* Tipos suportados:
|
|
15
|
+
* - `'text'` | `'email'` | `'password'` | `'date'` | `'phone'` — inputs de texto
|
|
16
|
+
* - `'select'` — dropdown/select com opções
|
|
17
|
+
* - `'radio'` — grupo de radio buttons
|
|
18
|
+
* - `'checkbox'` — grupo de checkboxes
|
|
19
|
+
* - `'textarea'` — área de texto
|
|
20
|
+
* - `'toggle'` — interruptor on/off
|
|
21
|
+
* - `'badges'` — input de tags/badges com sugestões
|
|
22
|
+
*
|
|
23
|
+
* @property type - Tipo do input.
|
|
24
|
+
* @property name - Nome do campo (usado como chave no objeto de dados).
|
|
25
|
+
* @property value - Valor inicial do campo.
|
|
26
|
+
* @property label - Label/rótulo do campo.
|
|
27
|
+
* @property placeholder - Texto placeholder.
|
|
28
|
+
* @property onChange - Callback ao alterar o valor.
|
|
29
|
+
* @property disabled - Desativa o campo.
|
|
30
|
+
* @property required - Marca como obrigatório.
|
|
31
|
+
* @property fill - Ocupa 2 colunas (true) ou 1 coluna (false/default).
|
|
32
|
+
* @property options - Opções para select, radio ou checkbox.
|
|
33
|
+
* @property multiple - Permite seleção múltipla (apenas select).
|
|
34
|
+
* @property rows - Número de linhas (apenas textarea).
|
|
35
|
+
* @property description - Texto descritivo (apenas toggle).
|
|
36
|
+
* @property suggestions - Sugestões de tags (apenas badges).
|
|
37
|
+
* @property maxTags - Número máximo de tags (apenas badges, 0 = ilimitado).
|
|
38
|
+
*/
|
|
39
|
+
export type FormInputConfig = {
|
|
40
|
+
type: "text" | "email" | "password" | "date" | "phone" | "select" | "radio" | "checkbox" | "textarea" | "toggle" | "badges";
|
|
41
|
+
name: string;
|
|
42
|
+
value?: string | string[] | boolean;
|
|
43
|
+
label?: string;
|
|
44
|
+
placeholder?: string;
|
|
45
|
+
onChange?: (value: string | string[] | boolean) => void;
|
|
46
|
+
disabled?: boolean;
|
|
47
|
+
required?: boolean;
|
|
48
|
+
fill?: boolean;
|
|
49
|
+
options?: FormOption[];
|
|
50
|
+
multiple?: boolean;
|
|
51
|
+
rows?: number;
|
|
52
|
+
description?: string;
|
|
53
|
+
suggestions?: string[];
|
|
54
|
+
maxTags?: number;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Props do componente Form — formulário com grid responsivo de 2 colunas.
|
|
58
|
+
* Organiza os inputs numa grid, onde cada campo pode ocupar 1 ou 2 colunas via `fill`.
|
|
59
|
+
*
|
|
60
|
+
* @property inputs - Array de configurações dos inputs do formulário.
|
|
61
|
+
* @property onSubmit - Callback ao submeter o formulário (objeto com name:value).
|
|
62
|
+
* @property submitText - Texto do botão de submissão.
|
|
63
|
+
* @property submitLoadingText - Texto durante carregamento.
|
|
64
|
+
* @property isLoading - Estado de carregamento.
|
|
65
|
+
* @property columns - Número de colunas da grid (padrão: 2).
|
|
66
|
+
* @property gap - Espaçamento entre os campos.
|
|
67
|
+
* @property class - Classe CSS extra no container.
|
|
68
|
+
*/
|
|
69
|
+
export type FormProps = {
|
|
70
|
+
inputs: FormInputConfig[];
|
|
71
|
+
onSubmit?: (data: Record<string, string | string[] | boolean>) => void;
|
|
72
|
+
submitText?: string;
|
|
73
|
+
submitLoadingText?: string;
|
|
74
|
+
isLoading?: boolean;
|
|
75
|
+
columns?: 1 | 2;
|
|
76
|
+
gap?: string;
|
|
77
|
+
class?: string;
|
|
78
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Label } from "../../label";
|
|
3
|
+
import { cn } from "../../../../utils";
|
|
4
|
+
import { Cancel01Icon } from "@hugeicons/core-free-icons";
|
|
5
|
+
import { HugeiconsIcon } from "@hugeicons/svelte";
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
label,
|
|
9
|
+
isLabel = false,
|
|
10
|
+
value = $bindable([]),
|
|
11
|
+
disabled = false,
|
|
12
|
+
required = false,
|
|
13
|
+
placeholder = "Add a tag...",
|
|
14
|
+
suggestions = [],
|
|
15
|
+
maxTags = 0,
|
|
16
|
+
class: className,
|
|
17
|
+
}: {
|
|
18
|
+
label?: string;
|
|
19
|
+
isLabel?: boolean;
|
|
20
|
+
value?: string[];
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
suggestions?: string[];
|
|
25
|
+
maxTags?: number;
|
|
26
|
+
class?: string;
|
|
27
|
+
} = $props();
|
|
28
|
+
|
|
29
|
+
let inputValue = $state("");
|
|
30
|
+
let isInputFocused = $state(false);
|
|
31
|
+
let showSuggestions = $state(false);
|
|
32
|
+
|
|
33
|
+
const filteredSuggestions = $derived(
|
|
34
|
+
suggestions.filter(
|
|
35
|
+
(s) =>
|
|
36
|
+
!value.includes(s) &&
|
|
37
|
+
s.toLowerCase().includes(inputValue.toLowerCase())
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
function addTag(tag: string) {
|
|
42
|
+
const trimmed = tag.trim();
|
|
43
|
+
if (!trimmed || disabled) return;
|
|
44
|
+
if (value.includes(trimmed)) return;
|
|
45
|
+
if (maxTags > 0 && value.length >= maxTags) return;
|
|
46
|
+
value = [...value, trimmed];
|
|
47
|
+
inputValue = "";
|
|
48
|
+
showSuggestions = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function removeTag(tag: string) {
|
|
52
|
+
if (disabled) return;
|
|
53
|
+
value = value.filter((t) => t !== tag);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
57
|
+
if (e.key === "Enter") {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
addTag(inputValue);
|
|
60
|
+
} else if (e.key === "Backspace" && !inputValue && value.length > 0) {
|
|
61
|
+
value = value.slice(0, -1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleInput(e: Event) {
|
|
66
|
+
inputValue = (e.target as HTMLInputElement).value;
|
|
67
|
+
showSuggestions = inputValue.length > 0 && filteredSuggestions.length > 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleFocus() {
|
|
71
|
+
isInputFocused = true;
|
|
72
|
+
if (inputValue.length > 0 && filteredSuggestions.length > 0) {
|
|
73
|
+
showSuggestions = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function handleBlur() {
|
|
78
|
+
isInputFocused = false;
|
|
79
|
+
setTimeout(() => (showSuggestions = false), 150);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function selectSuggestion(suggestion: string) {
|
|
83
|
+
addTag(suggestion);
|
|
84
|
+
inputValue = "";
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
{#if isLabel}
|
|
89
|
+
<div class={cn("flex w-full flex-col gap-2", className)}>
|
|
90
|
+
{#if label}
|
|
91
|
+
<Label>{label}</Label>
|
|
92
|
+
{/if}
|
|
93
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
94
|
+
<div
|
|
95
|
+
class={cn(
|
|
96
|
+
"flex min-h-[42px] flex-wrap items-center gap-1.5 rounded-md border border-input bg-card px-3 py-1.5 transition-colors",
|
|
97
|
+
isInputFocused && "ring-2 ring-ring ring-offset-2 ring-offset-background",
|
|
98
|
+
disabled && "cursor-not-allowed opacity-50"
|
|
99
|
+
)}
|
|
100
|
+
onclick={() => !disabled && document.getElementById(`badges-input-${label}`)?.focus()}
|
|
101
|
+
>
|
|
102
|
+
{#each value as tag (tag)}
|
|
103
|
+
<span class="inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2.5 py-0.5 text-xs font-medium text-foreground">
|
|
104
|
+
{tag}
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
{disabled}
|
|
108
|
+
class="ml-0.5 rounded-full p-0.5 transition-colors hover:bg-destructive/10 hover:text-destructive"
|
|
109
|
+
onclick={(e) => { e.stopPropagation(); removeTag(tag); }}
|
|
110
|
+
>
|
|
111
|
+
<HugeiconsIcon icon={Cancel01Icon} class="size-3" />
|
|
112
|
+
</button>
|
|
113
|
+
</span>
|
|
114
|
+
{/each}
|
|
115
|
+
|
|
116
|
+
{#if maxTags === 0 || value.length < maxTags}
|
|
117
|
+
<input
|
|
118
|
+
id="badges-input-{label}"
|
|
119
|
+
type="text"
|
|
120
|
+
bind:value={inputValue}
|
|
121
|
+
{placeholder}
|
|
122
|
+
{disabled}
|
|
123
|
+
{required}
|
|
124
|
+
class="min-w-[120px] flex-1 bg-transparent py-1 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed"
|
|
125
|
+
onkeydown={handleKeydown}
|
|
126
|
+
oninput={handleInput}
|
|
127
|
+
onfocus={handleFocus}
|
|
128
|
+
onblur={handleBlur}
|
|
129
|
+
/>
|
|
130
|
+
{/if}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{#if showSuggestions && filteredSuggestions.length > 0}
|
|
134
|
+
<div class="max-h-40 overflow-auto rounded-md border border-border bg-card shadow-md">
|
|
135
|
+
{#each filteredSuggestions as suggestion (suggestion)}
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
class="flex w-full items-center px-3 py-2 text-sm transition-colors hover:bg-muted"
|
|
139
|
+
onmousedown={(e) => { e.preventDefault(); selectSuggestion(suggestion); }}
|
|
140
|
+
>
|
|
141
|
+
{suggestion}
|
|
142
|
+
</button>
|
|
143
|
+
{/each}
|
|
144
|
+
</div>
|
|
145
|
+
{/if}
|
|
146
|
+
|
|
147
|
+
{#if maxTags > 0}
|
|
148
|
+
<p class="text-xs text-muted-foreground">
|
|
149
|
+
{value.length}/{maxTags} tags
|
|
150
|
+
</p>
|
|
151
|
+
{/if}
|
|
152
|
+
</div>
|
|
153
|
+
{:else}
|
|
154
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
155
|
+
<div
|
|
156
|
+
class={cn(
|
|
157
|
+
"flex min-h-[42px] flex-wrap items-center gap-1.5 rounded-md border border-input bg-card px-3 py-1.5 transition-colors",
|
|
158
|
+
isInputFocused && "ring-2 ring-ring ring-offset-2 ring-offset-background",
|
|
159
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
160
|
+
className
|
|
161
|
+
)}
|
|
162
|
+
onclick={() => !disabled && document.getElementById(`badges-input-${label}`)?.focus()}
|
|
163
|
+
>
|
|
164
|
+
{#each value as tag (tag)}
|
|
165
|
+
<span class="inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2.5 py-0.5 text-xs font-medium text-foreground">
|
|
166
|
+
{tag}
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
{disabled}
|
|
170
|
+
class="ml-0.5 rounded-full p-0.5 transition-colors hover:bg-destructive/10 hover:text-destructive"
|
|
171
|
+
onclick={(e) => { e.stopPropagation(); removeTag(tag); }}
|
|
172
|
+
>
|
|
173
|
+
<HugeiconsIcon icon={Cancel01Icon} class="size-3" />
|
|
174
|
+
</button>
|
|
175
|
+
</span>
|
|
176
|
+
{/each}
|
|
177
|
+
|
|
178
|
+
{#if maxTags === 0 || value.length < maxTags}
|
|
179
|
+
<input
|
|
180
|
+
id="badges-input-{label}"
|
|
181
|
+
type="text"
|
|
182
|
+
bind:value={inputValue}
|
|
183
|
+
{placeholder}
|
|
184
|
+
{disabled}
|
|
185
|
+
{required}
|
|
186
|
+
class="min-w-[120px] flex-1 bg-transparent py-1 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed"
|
|
187
|
+
onkeydown={handleKeydown}
|
|
188
|
+
oninput={handleInput}
|
|
189
|
+
onfocus={handleFocus}
|
|
190
|
+
onblur={handleBlur}
|
|
191
|
+
/>
|
|
192
|
+
{/if}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{#if showSuggestions && filteredSuggestions.length > 0}
|
|
196
|
+
<div class="max-h-40 overflow-auto rounded-md border border-border bg-card shadow-md">
|
|
197
|
+
{#each filteredSuggestions as suggestion (suggestion)}
|
|
198
|
+
<button
|
|
199
|
+
type="button"
|
|
200
|
+
class="flex w-full items-center px-3 py-2 text-sm transition-colors hover:bg-muted"
|
|
201
|
+
onmousedown={(e) => { e.preventDefault(); selectSuggestion(suggestion); }}
|
|
202
|
+
>
|
|
203
|
+
{suggestion}
|
|
204
|
+
</button>
|
|
205
|
+
{/each}
|
|
206
|
+
</div>
|
|
207
|
+
{/if}
|
|
208
|
+
|
|
209
|
+
{#if maxTags > 0}
|
|
210
|
+
<p class="text-xs text-muted-foreground">
|
|
211
|
+
{value.length}/{maxTags} tags
|
|
212
|
+
</p>
|
|
213
|
+
{/if}
|
|
214
|
+
{/if}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
label?: string;
|
|
3
|
+
isLabel?: boolean;
|
|
4
|
+
value?: string[];
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
suggestions?: string[];
|
|
9
|
+
maxTags?: number;
|
|
10
|
+
class?: string;
|
|
11
|
+
};
|
|
12
|
+
declare const InputBadges: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
13
|
+
type InputBadges = ReturnType<typeof InputBadges>;
|
|
14
|
+
export default InputBadges;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Label } from "../../label";
|
|
3
|
+
import { Checkbox } from "../../checkbox";
|
|
4
|
+
import { cn } from "../../../../utils";
|
|
5
|
+
|
|
6
|
+
type CheckboxOption = { value: string; label: string };
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
label,
|
|
10
|
+
isLabel = false,
|
|
11
|
+
value = $bindable([]),
|
|
12
|
+
disabled = false,
|
|
13
|
+
required = false,
|
|
14
|
+
options = [],
|
|
15
|
+
name = "",
|
|
16
|
+
class: className,
|
|
17
|
+
}: {
|
|
18
|
+
label?: string;
|
|
19
|
+
isLabel?: boolean;
|
|
20
|
+
value?: string[];
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
options?: CheckboxOption[];
|
|
24
|
+
name?: string;
|
|
25
|
+
class?: string;
|
|
26
|
+
} = $props();
|
|
27
|
+
|
|
28
|
+
function handleToggle(optionValue: string) {
|
|
29
|
+
if (disabled) return;
|
|
30
|
+
const idx = value.indexOf(optionValue);
|
|
31
|
+
if (idx >= 0) {
|
|
32
|
+
value = value.filter((v) => v !== optionValue);
|
|
33
|
+
} else {
|
|
34
|
+
value = [...value, optionValue];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isChecked(optionValue: string): boolean {
|
|
39
|
+
return value.includes(optionValue);
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
{#if isLabel}
|
|
44
|
+
<div class={cn("flex w-full flex-col gap-2", className)}>
|
|
45
|
+
{#if label}
|
|
46
|
+
<Label>{label}</Label>
|
|
47
|
+
{/if}
|
|
48
|
+
<div class="flex flex-wrap gap-4">
|
|
49
|
+
{#each options as option (option.value)}
|
|
50
|
+
<label
|
|
51
|
+
class={cn(
|
|
52
|
+
"flex cursor-pointer items-center gap-2 text-sm",
|
|
53
|
+
disabled && "cursor-not-allowed opacity-50"
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
<Checkbox
|
|
57
|
+
{disabled}
|
|
58
|
+
{name}
|
|
59
|
+
checked={isChecked(option.value)}
|
|
60
|
+
onCheckedChange={() => handleToggle(option.value)}
|
|
61
|
+
/>
|
|
62
|
+
<span class="text-foreground">{option.label}</span>
|
|
63
|
+
</label>
|
|
64
|
+
{/each}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
{:else}
|
|
68
|
+
<div class={cn("flex flex-wrap gap-4", className)}>
|
|
69
|
+
{#each options as option (option.value)}
|
|
70
|
+
<label
|
|
71
|
+
class={cn(
|
|
72
|
+
"flex cursor-pointer items-center gap-2 text-sm",
|
|
73
|
+
disabled && "cursor-not-allowed opacity-50"
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
<Checkbox
|
|
77
|
+
{disabled}
|
|
78
|
+
{name}
|
|
79
|
+
checked={isChecked(option.value)}
|
|
80
|
+
onCheckedChange={() => handleToggle(option.value)}
|
|
81
|
+
/>
|
|
82
|
+
<span class="text-foreground">{option.label}</span>
|
|
83
|
+
</label>
|
|
84
|
+
{/each}
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type CheckboxOption = {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
};
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
label?: string;
|
|
7
|
+
isLabel?: boolean;
|
|
8
|
+
value?: string[];
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
options?: CheckboxOption[];
|
|
12
|
+
name?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
};
|
|
15
|
+
declare const InputCheckbox: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
16
|
+
type InputCheckbox = ReturnType<typeof InputCheckbox>;
|
|
17
|
+
export default InputCheckbox;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Label } from "../../label";
|
|
3
|
+
import { cn } from "../../../../utils";
|
|
4
|
+
|
|
5
|
+
type RadioOption = { value: string; label: string };
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
label,
|
|
9
|
+
isLabel = false,
|
|
10
|
+
value = $bindable(""),
|
|
11
|
+
disabled = false,
|
|
12
|
+
required = false,
|
|
13
|
+
options = [],
|
|
14
|
+
name = "",
|
|
15
|
+
class: className,
|
|
16
|
+
}: {
|
|
17
|
+
label?: string;
|
|
18
|
+
isLabel?: boolean;
|
|
19
|
+
value?: string;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
options?: RadioOption[];
|
|
23
|
+
name?: string;
|
|
24
|
+
class?: string;
|
|
25
|
+
} = $props();
|
|
26
|
+
|
|
27
|
+
function handleSelect(option: RadioOption) {
|
|
28
|
+
if (disabled) return;
|
|
29
|
+
value = option.value;
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if isLabel}
|
|
34
|
+
<div class={cn("flex w-full flex-col gap-2", className)}>
|
|
35
|
+
{#if label}
|
|
36
|
+
<Label>{label}</Label>
|
|
37
|
+
{/if}
|
|
38
|
+
<div class="flex flex-wrap gap-4">
|
|
39
|
+
{#each options as option (option.value)}
|
|
40
|
+
<label
|
|
41
|
+
class={cn(
|
|
42
|
+
"flex cursor-pointer items-center gap-2 text-sm",
|
|
43
|
+
disabled && "cursor-not-allowed opacity-50"
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
<input
|
|
47
|
+
type="radio"
|
|
48
|
+
{name}
|
|
49
|
+
{disabled}
|
|
50
|
+
{required}
|
|
51
|
+
checked={value === option.value}
|
|
52
|
+
onchange={() => handleSelect(option)}
|
|
53
|
+
class="size-4 border-input text-primary accent-primary"
|
|
54
|
+
/>
|
|
55
|
+
<span class="text-foreground">{option.label}</span>
|
|
56
|
+
</label>
|
|
57
|
+
{/each}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
{:else}
|
|
61
|
+
<div class={cn("flex flex-wrap gap-4", className)}>
|
|
62
|
+
{#each options as option (option.value)}
|
|
63
|
+
<label
|
|
64
|
+
class={cn(
|
|
65
|
+
"flex cursor-pointer items-center gap-2 text-sm",
|
|
66
|
+
disabled && "cursor-not-allowed opacity-50"
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
<input
|
|
70
|
+
type="radio"
|
|
71
|
+
{name}
|
|
72
|
+
{disabled}
|
|
73
|
+
{required}
|
|
74
|
+
checked={value === option.value}
|
|
75
|
+
onchange={() => handleSelect(option)}
|
|
76
|
+
class="size-4 border-input text-primary accent-primary"
|
|
77
|
+
/>
|
|
78
|
+
<span class="text-foreground">{option.label}</span>
|
|
79
|
+
</label>
|
|
80
|
+
{/each}
|
|
81
|
+
</div>
|
|
82
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type RadioOption = {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
};
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
label?: string;
|
|
7
|
+
isLabel?: boolean;
|
|
8
|
+
value?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
options?: RadioOption[];
|
|
12
|
+
name?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
};
|
|
15
|
+
declare const InputRadio: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
16
|
+
type InputRadio = ReturnType<typeof InputRadio>;
|
|
17
|
+
export default InputRadio;
|