mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-27 03:16:33 +07:00
continue dict test reference
This commit is contained in:
parent
ac4e39ce70
commit
a54e1b9d23
@ -41,10 +41,33 @@ export const testCalInitialForm = {
|
|||||||
FormulaCode: ""
|
FormulaCode: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const refNumInitialForm = {
|
||||||
|
RefNumID: "",
|
||||||
|
SiteID: "",
|
||||||
|
TestSiteID: "",
|
||||||
|
SpcType: "",
|
||||||
|
Sex: "",
|
||||||
|
Criteria: "",
|
||||||
|
AgeStart: "",
|
||||||
|
AgeEnd: "",
|
||||||
|
NumRefType: "",
|
||||||
|
RangeType: "",
|
||||||
|
LowSign: "",
|
||||||
|
Low: "",
|
||||||
|
HighSign: "",
|
||||||
|
High: "",
|
||||||
|
Display: "",
|
||||||
|
Flag: "",
|
||||||
|
Interpretation: "",
|
||||||
|
Notes: "",
|
||||||
|
}
|
||||||
|
|
||||||
export const testDefaultErrors = {};
|
export const testDefaultErrors = {};
|
||||||
|
|
||||||
export const testCalDefaultErrors = {};
|
export const testCalDefaultErrors = {};
|
||||||
|
|
||||||
|
export const refNumDefaultErrors = {};
|
||||||
|
|
||||||
export const testFormFields = [
|
export const testFormFields = [
|
||||||
{
|
{
|
||||||
title: "Basic Information",
|
title: "Basic Information",
|
||||||
@ -347,6 +370,146 @@ export const testCalFormFields = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const refNumFormFields = [
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "SiteID",
|
||||||
|
label: "Site",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.SITE}`,
|
||||||
|
valueKey: "SiteID",
|
||||||
|
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`,
|
||||||
|
fullWidth: false
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Criteria Definition",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "Sex",
|
||||||
|
label: "Sex",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/sex`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "SpcType",
|
||||||
|
label: "Specimen Type",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/specimen_type`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "AgeStart",
|
||||||
|
label: "Age Start",
|
||||||
|
required: false,
|
||||||
|
type: "agejoin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "AgeEnd",
|
||||||
|
label: "Age End",
|
||||||
|
required: false,
|
||||||
|
type: "agejoin",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Reference Range Configuration",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "NumRefType",
|
||||||
|
label: "Reference Type",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "RangeType",
|
||||||
|
label: "Range Type",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/range_type`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "LowSign",
|
||||||
|
label: "Low Sign - Value",
|
||||||
|
required: false,
|
||||||
|
type: "signvalue",
|
||||||
|
txtKey: "Low",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/math_sign`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "HighSign",
|
||||||
|
label: "High Sign - Value",
|
||||||
|
required: false,
|
||||||
|
type: "signvalue",
|
||||||
|
txtKey: "High",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/math_sign`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Flag & Interpretation",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "Flag",
|
||||||
|
label: "Flag",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Interpretation",
|
||||||
|
label: "Interpretation",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "Notes",
|
||||||
|
label: "Notes",
|
||||||
|
required: false,
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function getTestFormActions(handlers) {
|
export function getTestFormActions(handlers) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,7 +6,10 @@
|
|||||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||||
import * as Tabs from "$lib/components/ui/tabs/index.js";
|
import * as Tabs from "$lib/components/ui/tabs/index.js";
|
||||||
import { useForm } from "$lib/components/composable/use-form.svelte";
|
import { useForm } from "$lib/components/composable/use-form.svelte";
|
||||||
import { testCalInitialForm, testCalDefaultErrors, testCalFormFields } from "$lib/components/dictionary/test/config/test-form-config";
|
import { testCalInitialForm, testCalDefaultErrors, testCalFormFields, refNumInitialForm, refNumFormFields } from "$lib/components/dictionary/test/config/test-form-config";
|
||||||
|
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
||||||
|
import RefNum from "./reference/ref-num.svelte";
|
||||||
|
import RefTxt from "./reference/ref-txt.svelte";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -24,6 +27,16 @@
|
|||||||
editEndpoint: null,
|
editEndpoint: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const refNumState = useForm({
|
||||||
|
schema: null,
|
||||||
|
initialForm: refNumInitialForm,
|
||||||
|
defaultErrors: {},
|
||||||
|
mode: 'create',
|
||||||
|
modeOpt: 'default',
|
||||||
|
saveEndpoint: null,
|
||||||
|
editEndpoint: null,
|
||||||
|
})
|
||||||
|
|
||||||
const helpers = useDictionaryForm(formState);
|
const helpers = useDictionaryForm(formState);
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
@ -107,6 +120,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let refComponent = $derived.by(() => {
|
||||||
|
const refType = formState.form.RefType;
|
||||||
|
if (refType === 'RANGE' || refType === 'THOLD') return 'numeric';
|
||||||
|
if (refType === 'TEXT' || refType === 'VSET') return 'text';
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
let activeTab = $state('definition');
|
let activeTab = $state('definition');
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@ -117,7 +137,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPageContainer title="Create Test" {primaryAction} {secondaryActions} {actions}>
|
<FormPageContainer title="Create Test" {primaryAction} {secondaryActions} {actions}>
|
||||||
<Tabs.Root bind:value={activeTab} class="w-full">
|
<Tabs.Root bind:value={activeTab} class="w-full h-full">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
{#if availableTabs.includes('definition')}
|
{#if availableTabs.includes('definition')}
|
||||||
<Tabs.Trigger value="definition">Definition</Tabs.Trigger>
|
<Tabs.Trigger value="definition">Definition</Tabs.Trigger>
|
||||||
@ -157,7 +177,17 @@
|
|||||||
map
|
map
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
<Tabs.Content value="reference">
|
<Tabs.Content value="reference">
|
||||||
ref
|
<div class="w-full h-full flex items-start">
|
||||||
|
{#if refComponent === 'numeric'}
|
||||||
|
<RefNum {refNumState} {refNumFormFields} />
|
||||||
|
{:else if refComponent === 'text'}
|
||||||
|
<RefTxt {formState} />
|
||||||
|
{:else}
|
||||||
|
<div class="h-full w-full flex items-center">
|
||||||
|
<ReusableEmpty desc="Select a Reference Type" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
</Tabs.Root>
|
</Tabs.Root>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
<script>
|
||||||
|
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
||||||
|
import { Separator } from "$lib/components/ui/separator/index.js";
|
||||||
|
import * as Table from "$lib/components/ui/table/index.js";
|
||||||
|
import { Button } from "$lib/components/ui/button/index.js";
|
||||||
|
import { Badge } from "$lib/components/ui/badge/index.js";
|
||||||
|
|
||||||
|
let props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 w-full">
|
||||||
|
<div>
|
||||||
|
<DictionaryFormRenderer
|
||||||
|
formState={props.refNumState}
|
||||||
|
formFields={props.refNumFormFields}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
class="cursor-pointer mt-1 ms-2"
|
||||||
|
>
|
||||||
|
Insert
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div>
|
||||||
|
<Table.Root>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row class="hover:bg-transparent">
|
||||||
|
<Table.Head class="">Specimen Type</Table.Head>
|
||||||
|
<Table.Head class="">Sex</Table.Head>
|
||||||
|
<Table.Head class="">Age Range</Table.Head>
|
||||||
|
<Table.Head class="">Type</Table.Head>
|
||||||
|
<Table.Head class="">Range/Threshold</Table.Head>
|
||||||
|
<Table.Head class="">Flag</Table.Head>
|
||||||
|
<Table.Head class="">Interpretation</Table.Head>
|
||||||
|
<Table.Head class="">Notes</Table.Head>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body>
|
||||||
|
<Table.Row
|
||||||
|
class="cursor-pointer hover:bg-muted/50"
|
||||||
|
>
|
||||||
|
<Table.Cell>Whole Blood</Table.Cell>
|
||||||
|
<Table.Cell class="font-medium">Female</Table.Cell>
|
||||||
|
<Table.Cell class="">1Y 0M 0D - 10Y 0M 0D</Table.Cell>
|
||||||
|
<Table.Cell class="text-muted-foreground">Reference Range</Table.Cell>
|
||||||
|
<Table.Cell class="font-medium"><Badge>Range</Badge></Table.Cell>
|
||||||
|
<Table.Cell class="font-medium">L</Table.Cell>
|
||||||
|
<Table.Cell class="font-medium">Interpretation</Table.Cell>
|
||||||
|
<Table.Cell class="font-medium">Testing</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
txt
|
||||||
@ -8,6 +8,8 @@
|
|||||||
import { Spinner } from "$lib/components/ui/spinner/index.js";
|
import { Spinner } from "$lib/components/ui/spinner/index.js";
|
||||||
import ReusableCalendar from "$lib/components/reusable/reusable-calendar.svelte";
|
import ReusableCalendar from "$lib/components/reusable/reusable-calendar.svelte";
|
||||||
import { Badge } from "$lib/components/ui/badge/index.js";
|
import { Badge } from "$lib/components/ui/badge/index.js";
|
||||||
|
import * as InputGroup from "$lib/components/ui/input-group/index.js";
|
||||||
|
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
formState,
|
formState,
|
||||||
@ -19,6 +21,7 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let searchQuery = $state({});
|
let searchQuery = $state({});
|
||||||
|
let dropdownOpen = $state({});
|
||||||
|
|
||||||
function getFilteredOptions(key) {
|
function getFilteredOptions(key) {
|
||||||
const query = searchQuery[key] || "";
|
const query = searchQuery[key] || "";
|
||||||
@ -60,7 +63,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet Fieldset({ key, label, required, type, optionsEndpoint, options, validateOn, dependsOn, endpointParamKey, valueKey, labelKey })}
|
{#snippet Fieldset({ key, label, required, type, optionsEndpoint, options, validateOn, dependsOn, endpointParamKey, valueKey, labelKey, txtKey })}
|
||||||
<div class="flex w-full flex-col gap-1.5">
|
<div class="flex w-full flex-col gap-1.5">
|
||||||
<div class="flex justify-between items-center w-full">
|
<div class="flex justify-between items-center w-full">
|
||||||
<Label>{label}</Label>
|
<Label>{label}</Label>
|
||||||
@ -277,60 +280,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
|
|
||||||
<!-- {:else if type === "selectmultiple"}
|
|
||||||
{@const selectedLabels = Array.isArray(formState.form[key])
|
|
||||||
? formState.form[key]
|
|
||||||
.map(val => formState.selectOptions?.[key]?.find(opt => opt.value === val)?.label)
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(", ")
|
|
||||||
: "Choose"}
|
|
||||||
{@const filteredOptions = getFilteredOptions(key)}
|
|
||||||
<Select.Root type="multiple" bind:value={formState.form[key]}
|
|
||||||
onValueChange={(vals) => {
|
|
||||||
formState.form[key] = vals;
|
|
||||||
if (validateOn?.includes("input")) {
|
|
||||||
formState.validateField?.(key, formState.form[key], false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
if (open && optionsEndpoint) {
|
|
||||||
formState.fetchOptions?.(
|
|
||||||
{ key, optionsEndpoint, dependsOn, endpointParamKey, valueKey, labelKey },
|
|
||||||
formState.form
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Select.Trigger class="w-full truncate">
|
|
||||||
{selectedLabels}
|
|
||||||
</Select.Trigger>
|
|
||||||
<Select.Content>
|
|
||||||
<div class="p-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search..."
|
|
||||||
class="w-full border rounded px-2 py-1 text-sm"
|
|
||||||
bind:value={searchQuery[key]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if formState.loadingOptions?.[key]}
|
|
||||||
<Select.Item disabled value="loading">Loading...</Select.Item>
|
|
||||||
{:else}
|
|
||||||
{#if !required}
|
|
||||||
<Select.Item value="">- None -</Select.Item>
|
|
||||||
{/if}
|
|
||||||
{#each filteredOptions as option}
|
|
||||||
<Select.Item value={option.value}
|
|
||||||
disabled={key === "ResultType" && disabledResultTypes.includes(option.value) ||
|
|
||||||
key === "RefType" && disabledReferenceTypes.includes(option.value)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</Select.Item>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Root> -->
|
|
||||||
{:else if type === "date"}
|
{:else if type === "date"}
|
||||||
<ReusableCalendar
|
<ReusableCalendar
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
@ -341,6 +290,79 @@
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{:else if type === "signvalue"}
|
||||||
|
<InputGroup.Root>
|
||||||
|
<InputGroup.Input
|
||||||
|
placeholder="Type here"
|
||||||
|
bind:value={formState.form[txtKey]}
|
||||||
|
/>
|
||||||
|
<InputGroup.Addon align="inline-start">
|
||||||
|
<DropdownMenu.Root
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
dropdownOpen[key] = open;
|
||||||
|
if (open && optionsEndpoint) {
|
||||||
|
formState.fetchOptions?.(
|
||||||
|
{ key, optionsEndpoint, valueKey, labelKey },
|
||||||
|
formState.form
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Trigger>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<InputGroup.Button
|
||||||
|
{...props}
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
{formState.selectOptions?.[key]?.find(
|
||||||
|
opt => opt.value === formState.form[key]
|
||||||
|
)?.label || 'Choose'}
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</InputGroup.Button>
|
||||||
|
{/snippet}
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Content align="start">
|
||||||
|
{#if formState.loadingOptions?.[key]}
|
||||||
|
<DropdownMenu.Item disabled>
|
||||||
|
Loading...
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{:else}
|
||||||
|
{#each formState.selectOptions?.[key] || [] as option}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
onclick={() => {
|
||||||
|
formState.form[key] = option.value;
|
||||||
|
dropdownOpen[key] = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</InputGroup.Addon>
|
||||||
|
</InputGroup.Root>
|
||||||
|
{:else if type === "agejoin"}
|
||||||
|
<div class="flex items-center gap-2 w-full">
|
||||||
|
<InputGroup.Root>
|
||||||
|
<InputGroup.Input type="number" />
|
||||||
|
<InputGroup.Addon align="inline-end">
|
||||||
|
<InputGroup.Text>Year</InputGroup.Text>
|
||||||
|
</InputGroup.Addon>
|
||||||
|
</InputGroup.Root>
|
||||||
|
<InputGroup.Root>
|
||||||
|
<InputGroup.Input type="number" />
|
||||||
|
<InputGroup.Addon align="inline-end">
|
||||||
|
<InputGroup.Text>Month</InputGroup.Text>
|
||||||
|
</InputGroup.Addon>
|
||||||
|
</InputGroup.Root>
|
||||||
|
<InputGroup.Root>
|
||||||
|
<InputGroup.Input type="number" />
|
||||||
|
<InputGroup.Addon align="inline-end">
|
||||||
|
<InputGroup.Text>Day</InputGroup.Text>
|
||||||
|
</InputGroup.Addon>
|
||||||
|
</InputGroup.Root>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
22
src/lib/components/ui/input-group/index.js
Normal file
22
src/lib/components/ui/input-group/index.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Root from "./input-group.svelte";
|
||||||
|
import Addon from "./input-group-addon.svelte";
|
||||||
|
import Button from "./input-group-button.svelte";
|
||||||
|
import Input from "./input-group-input.svelte";
|
||||||
|
import Text from "./input-group-text.svelte";
|
||||||
|
import Textarea from "./input-group-textarea.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Addon,
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Text,
|
||||||
|
Textarea,
|
||||||
|
//
|
||||||
|
Root as InputGroup,
|
||||||
|
Addon as InputGroupAddon,
|
||||||
|
Button as InputGroupButton,
|
||||||
|
Input as InputGroupInput,
|
||||||
|
Text as InputGroupText,
|
||||||
|
Textarea as InputGroupTextarea,
|
||||||
|
};
|
||||||
49
src/lib/components/ui/input-group/input-group-addon.svelte
Normal file
49
src/lib/components/ui/input-group/input-group-addon.svelte
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<script module>
|
||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
export const inputGroupAddonVariants = tv({
|
||||||
|
base: "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
||||||
|
variants: {
|
||||||
|
align: {
|
||||||
|
"inline-start":
|
||||||
|
"order-first ps-3 has-[>button]:ms-[-0.45rem] has-[>kbd]:ms-[-0.35rem]",
|
||||||
|
"inline-end":
|
||||||
|
"order-last pe-3 has-[>button]:me-[-0.45rem] has-[>kbd]:me-[-0.35rem]",
|
||||||
|
"block-start":
|
||||||
|
"order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
|
||||||
|
"block-end":
|
||||||
|
"order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
align: "inline-start",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
align = "inline-start",
|
||||||
|
...restProps
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
role="group"
|
||||||
|
data-slot="input-group-addon"
|
||||||
|
data-align={align}
|
||||||
|
class={cn(inputGroupAddonVariants({ align }), className)}
|
||||||
|
onclick={(e) => {
|
||||||
|
if ((e.target).closest("button")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.currentTarget.parentElement?.querySelector("input")?.focus();
|
||||||
|
}}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
44
src/lib/components/ui/input-group/input-group-button.svelte
Normal file
44
src/lib/components/ui/input-group/input-group-button.svelte
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<script module>
|
||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
const inputGroupButtonVariants = tv({
|
||||||
|
base: "flex items-center gap-2 text-sm shadow-none",
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
|
||||||
|
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
|
||||||
|
"icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
||||||
|
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: "xs",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import { Button } from "$lib/components/ui/button/index.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
type = "button",
|
||||||
|
variant = "ghost",
|
||||||
|
size = "xs",
|
||||||
|
...restProps
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
bind:ref
|
||||||
|
{type}
|
||||||
|
data-size={size}
|
||||||
|
{variant}
|
||||||
|
class={cn(inputGroupButtonVariants({ size }), className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</Button>
|
||||||
22
src/lib/components/ui/input-group/input-group-input.svelte
Normal file
22
src/lib/components/ui/input-group/input-group-input.svelte
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import { Input } from "$lib/components/ui/input/index.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
value = $bindable(),
|
||||||
|
class: className,
|
||||||
|
...props
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
bind:ref
|
||||||
|
data-slot="input-group-control"
|
||||||
|
class={cn(
|
||||||
|
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
bind:value
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
20
src/lib/components/ui/input-group/input-group-text.svelte
Normal file
20
src/lib/components/ui/input-group/input-group-text.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
bind:this={ref}
|
||||||
|
class={cn(
|
||||||
|
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</span>
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import { Textarea } from "$lib/components/ui/textarea/index.js";
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
value = $bindable(),
|
||||||
|
class: className,
|
||||||
|
...props
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
bind:ref
|
||||||
|
data-slot="input-group-control"
|
||||||
|
class={cn(
|
||||||
|
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
bind:value
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
36
src/lib/components/ui/input-group/input-group.svelte
Normal file
36
src/lib/components/ui/input-group/input-group.svelte
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="input-group"
|
||||||
|
role="group"
|
||||||
|
class={cn(
|
||||||
|
"group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none",
|
||||||
|
"h-9 has-[>textarea]:h-auto",
|
||||||
|
|
||||||
|
// Variants based on alignment.
|
||||||
|
"has-[>[data-align=inline-start]]:[&>input]:ps-2",
|
||||||
|
"has-[>[data-align=inline-end]]:[&>input]:pe-2",
|
||||||
|
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
||||||
|
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
||||||
|
|
||||||
|
// Focus state.
|
||||||
|
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
|
||||||
|
|
||||||
|
// Error state.
|
||||||
|
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
||||||
|
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
7
src/lib/components/ui/textarea/index.js
Normal file
7
src/lib/components/ui/textarea/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./textarea.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Textarea,
|
||||||
|
};
|
||||||
21
src/lib/components/ui/textarea/textarea.svelte
Normal file
21
src/lib/components/ui/textarea/textarea.svelte
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
value = $bindable(),
|
||||||
|
class: className,
|
||||||
|
"data-slot": dataSlot = "textarea",
|
||||||
|
...restProps
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot={dataSlot}
|
||||||
|
class={cn(
|
||||||
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
bind:value
|
||||||
|
{...restProps}
|
||||||
|
></textarea>
|
||||||
Loading…
x
Reference in New Issue
Block a user