mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-28 03:45:55 +07:00
initial dictionary test definition + calculation
This commit is contained in:
parent
a3811bba70
commit
ac4e39ce70
@ -48,7 +48,7 @@
|
|||||||
const option = detailFormState.selectOptions[fieldKey].find(opt => opt.value === value);
|
const option = detailFormState.selectOptions[fieldKey].find(opt => opt.value === value);
|
||||||
return option?.label || value || "-";
|
return option?.label || value || "-";
|
||||||
}
|
}
|
||||||
$inspect(detailFormState.selectOptions)
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
formFields.forEach(group => {
|
formFields.forEach(group => {
|
||||||
|
|||||||
@ -34,8 +34,17 @@ export const testInitialForm = {
|
|||||||
Level: "",
|
Level: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const testCalInitialForm = {
|
||||||
|
TestCalID: "",
|
||||||
|
TestSiteID: "",
|
||||||
|
FormulaInput: "",
|
||||||
|
FormulaCode: ""
|
||||||
|
}
|
||||||
|
|
||||||
export const testDefaultErrors = {};
|
export const testDefaultErrors = {};
|
||||||
|
|
||||||
|
export const testCalDefaultErrors = {};
|
||||||
|
|
||||||
export const testFormFields = [
|
export const testFormFields = [
|
||||||
{
|
{
|
||||||
title: "Basic Information",
|
title: "Basic Information",
|
||||||
@ -75,8 +84,8 @@ export const testFormFields = [
|
|||||||
key: "TestType",
|
key: "TestType",
|
||||||
label: "Test Type",
|
label: "Test Type",
|
||||||
required: true,
|
required: true,
|
||||||
type: "text",
|
type: "select",
|
||||||
validateOn: ["input"]
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/test_type`,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -130,14 +139,14 @@ export const testFormFields = [
|
|||||||
{
|
{
|
||||||
key: "ResultType",
|
key: "ResultType",
|
||||||
label: "Result Type",
|
label: "Result Type",
|
||||||
required: true,
|
required: false,
|
||||||
type: "select",
|
type: "select",
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/result_type`,
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/result_type`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "RefType",
|
key: "RefType",
|
||||||
label: "Reference Type",
|
label: "Reference Type",
|
||||||
required: true,
|
required: false,
|
||||||
type: "select",
|
type: "select",
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/reference_type`,
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/reference_type`,
|
||||||
}
|
}
|
||||||
@ -151,7 +160,8 @@ export const testFormFields = [
|
|||||||
label: "Value Set",
|
label: "Value Set",
|
||||||
required: false,
|
required: false,
|
||||||
type: "select",
|
type: "select",
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/service_classes`,
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/v_category`,
|
||||||
|
fullWidth: false
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -170,6 +180,11 @@ export const testFormFields = [
|
|||||||
required: false,
|
required: false,
|
||||||
type: "text",
|
type: "text",
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
{
|
{
|
||||||
key: "Unit2",
|
key: "Unit2",
|
||||||
label: "Unit 2",
|
label: "Unit 2",
|
||||||
@ -192,6 +207,7 @@ export const testFormFields = [
|
|||||||
label: "Expected TAT",
|
label: "Expected TAT",
|
||||||
required: false,
|
required: false,
|
||||||
type: "select",
|
type: "select",
|
||||||
|
fullWidth: false
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -299,6 +315,38 @@ export const testFormFields = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const testCalFormFields = [
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "FormulaInput",
|
||||||
|
label: "Input Parameter",
|
||||||
|
required: false,
|
||||||
|
type: "selectmultiple",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.TEST}`,
|
||||||
|
valueKey: "TestSiteID",
|
||||||
|
labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "FormulaCode",
|
||||||
|
label: "Formula",
|
||||||
|
required: false,
|
||||||
|
type: "textarea",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function getTestFormActions(handlers) {
|
export function getTestFormActions(handlers) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,6 +4,9 @@
|
|||||||
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
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 { useForm } from "$lib/components/composable/use-form.svelte";
|
||||||
|
import { testCalInitialForm, testCalDefaultErrors, testCalFormFields } from "$lib/components/dictionary/test/config/test-form-config";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -11,6 +14,16 @@
|
|||||||
|
|
||||||
const { formState } = masterDetail;
|
const { formState } = masterDetail;
|
||||||
|
|
||||||
|
const calFormState = useForm({
|
||||||
|
schema: null,
|
||||||
|
initialForm: testCalInitialForm,
|
||||||
|
defaultErrors: {},
|
||||||
|
mode: 'create',
|
||||||
|
modeOpt: 'default',
|
||||||
|
saveEndpoint: null,
|
||||||
|
editEndpoint: null,
|
||||||
|
});
|
||||||
|
|
||||||
const helpers = useDictionaryForm(formState);
|
const helpers = useDictionaryForm(formState);
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
@ -38,14 +51,116 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const secondaryActions = [];
|
const secondaryActions = [];
|
||||||
|
|
||||||
|
// ==================================================================
|
||||||
|
|
||||||
|
let availableTabs = $derived.by(() => {
|
||||||
|
const testType = formState?.form?.TestType;
|
||||||
|
|
||||||
|
switch(testType) {
|
||||||
|
case 'TEST':
|
||||||
|
case 'PARAM':
|
||||||
|
return ['definition', 'map', 'reference'];
|
||||||
|
case 'CALC':
|
||||||
|
return ['definition', 'calculation', 'map', 'reference'];
|
||||||
|
case 'GROUP':
|
||||||
|
return ['definition', 'group'];
|
||||||
|
default:
|
||||||
|
return ['definition'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let disabledResultTypes = $derived.by(() => {
|
||||||
|
const testType = formState?.form?.TestType;
|
||||||
|
|
||||||
|
switch(testType) {
|
||||||
|
case 'TEST':
|
||||||
|
case 'PARAM':
|
||||||
|
return [];
|
||||||
|
case 'CALC':
|
||||||
|
return ['RANGE', 'TEXT', 'VSET', 'NORES'];
|
||||||
|
case 'GROUP':
|
||||||
|
return ['NMRIC', 'RANGE', 'TEXT', 'VSET', 'NORES'];
|
||||||
|
case 'TITLE':
|
||||||
|
return ['NMRIC', 'RANGE', 'TEXT', 'VSET', 'NORES'];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let disabledReferenceTypes = $derived.by(() => {
|
||||||
|
const resultType = formState?.form?.ResultType;
|
||||||
|
|
||||||
|
switch(resultType) {
|
||||||
|
case 'NMRIC':
|
||||||
|
return ['TEXT', 'VSET', 'NOREF'];
|
||||||
|
case 'RANGE':
|
||||||
|
return ['TEXT', 'VSET', 'NOREF'];
|
||||||
|
case 'TEXT':
|
||||||
|
return ['RANGE', 'THOLD', 'VSET', 'NOREF'];
|
||||||
|
case 'VSET':
|
||||||
|
return ['RANGE', 'THOLD', 'TEXT', 'NOREF'];
|
||||||
|
case 'NORES':
|
||||||
|
return ['RANGE', 'THOLD', 'TEXT', 'VSET'];
|
||||||
|
default:
|
||||||
|
return ['RANGE', 'THOLD', 'TEXT', 'VSET', 'NOREF'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let activeTab = $state('definition');
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!availableTabs.includes(activeTab)) {
|
||||||
|
activeTab = availableTabs[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPageContainer title="Test Location" {primaryAction} {secondaryActions} {actions}>
|
<FormPageContainer title="Create Test" {primaryAction} {secondaryActions} {actions}>
|
||||||
<DictionaryFormRenderer
|
<Tabs.Root bind:value={activeTab} class="w-full">
|
||||||
{formState}
|
<Tabs.List>
|
||||||
formFields={formFields}
|
{#if availableTabs.includes('definition')}
|
||||||
mode="create"
|
<Tabs.Trigger value="definition">Definition</Tabs.Trigger>
|
||||||
/>
|
{/if}
|
||||||
|
{#if availableTabs.includes('calculation')}
|
||||||
|
<Tabs.Trigger value="calculation">Calculation</Tabs.Trigger>
|
||||||
|
{/if}
|
||||||
|
{#if availableTabs.includes('group')}
|
||||||
|
<Tabs.Trigger value="group">Group</Tabs.Trigger>
|
||||||
|
{/if}
|
||||||
|
{#if availableTabs.includes('reference')}
|
||||||
|
<Tabs.Trigger value="reference">Reference</Tabs.Trigger>
|
||||||
|
{/if}
|
||||||
|
{#if availableTabs.includes('map')}
|
||||||
|
<Tabs.Trigger value="map">Map</Tabs.Trigger>
|
||||||
|
{/if}
|
||||||
|
</Tabs.List>
|
||||||
|
<Tabs.Content value="definition">
|
||||||
|
<DictionaryFormRenderer
|
||||||
|
{formState}
|
||||||
|
formFields={formFields}
|
||||||
|
mode="create"
|
||||||
|
{disabledResultTypes}
|
||||||
|
{disabledReferenceTypes}
|
||||||
|
/>
|
||||||
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value="calculation">
|
||||||
|
<DictionaryFormRenderer
|
||||||
|
formState={calFormState}
|
||||||
|
formFields={testCalFormFields}
|
||||||
|
/>
|
||||||
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value="group">
|
||||||
|
group
|
||||||
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value="map">
|
||||||
|
map
|
||||||
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value="reference">
|
||||||
|
ref
|
||||||
|
</Tabs.Content>
|
||||||
|
</Tabs.Root>
|
||||||
|
|
||||||
</FormPageContainer>
|
</FormPageContainer>
|
||||||
|
|
||||||
<ReusableAlertDialog
|
<ReusableAlertDialog
|
||||||
|
|||||||
@ -7,11 +7,15 @@
|
|||||||
import { Label } from "$lib/components/ui/label/index.js";
|
import { Label } from "$lib/components/ui/label/index.js";
|
||||||
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";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
formState,
|
formState,
|
||||||
formFields,
|
formFields,
|
||||||
mode = 'create'
|
mode = 'create',
|
||||||
|
disabledResultTypes = [],
|
||||||
|
disabledReferenceTypes = [],
|
||||||
|
...props
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let searchQuery = $state({});
|
let searchQuery = $state({});
|
||||||
@ -137,8 +141,6 @@
|
|||||||
}
|
}
|
||||||
if (key === "Province") {
|
if (key === "Province") {
|
||||||
formState.form.City = "";
|
formState.form.City = "";
|
||||||
// formState.selectOptions.City = [];
|
|
||||||
// formState.lastFetched.City = null;
|
|
||||||
if (formState.selectOptions) {
|
if (formState.selectOptions) {
|
||||||
formState.selectOptions.City = [];
|
formState.selectOptions.City = [];
|
||||||
}
|
}
|
||||||
@ -146,6 +148,34 @@
|
|||||||
formState.lastFetched.City = null;
|
formState.lastFetched.City = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (key === "TestType") {
|
||||||
|
formState.form[key] = val;
|
||||||
|
|
||||||
|
formState.form.ResultType = "";
|
||||||
|
formState.errors.ResultType = null;
|
||||||
|
formState.form.RefType = "";
|
||||||
|
formState.errors.RefType = null;
|
||||||
|
|
||||||
|
if (val === 'CALC') {
|
||||||
|
formState.form.ResultType = 'NMRIC';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key === "ResultType") {
|
||||||
|
formState.form[key] = val;
|
||||||
|
|
||||||
|
formState.form.RefType = "";
|
||||||
|
formState.errors.RefType = null;
|
||||||
|
|
||||||
|
if (val === 'TEXT') {
|
||||||
|
formState.form.RefType = 'TEXT';
|
||||||
|
}
|
||||||
|
if (val === 'VSET') {
|
||||||
|
formState.form.RefType = 'VSET';
|
||||||
|
}
|
||||||
|
if (val === 'NORES') {
|
||||||
|
formState.form.RefType = 'NOREF';
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (open && optionsEndpoint) {
|
if (open && optionsEndpoint) {
|
||||||
@ -175,13 +205,132 @@
|
|||||||
<Select.Item value="">- None -</Select.Item>
|
<Select.Item value="">- None -</Select.Item>
|
||||||
{/if}
|
{/if}
|
||||||
{#each filteredOptions as option}
|
{#each filteredOptions as option}
|
||||||
<Select.Item value={option.value}>
|
<Select.Item value={option.value}
|
||||||
|
disabled={key === "ResultType" && disabledResultTypes.includes(option.value) ||
|
||||||
|
key === "RefType" && disabledReferenceTypes.includes(option.value)}
|
||||||
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
|
{:else if type === "selectmultiple"}
|
||||||
|
{@const filteredOptions = getFilteredOptions(key)}
|
||||||
|
{@const selectedValues = Array.isArray(formState.form[key]) ? formState.form[key] : []}
|
||||||
|
{@const selectedOptions = selectedValues
|
||||||
|
.map(val => formState.selectOptions?.[key]?.find(opt => opt.value === val))
|
||||||
|
.filter(Boolean)}
|
||||||
|
|
||||||
|
<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 min-h-[42px]">
|
||||||
|
{#if selectedOptions.length === 0}
|
||||||
|
<span class="text-muted-foreground">Choose</span>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
{#each selectedOptions as option}
|
||||||
|
<Badge variant="secondary" class="text-xs">
|
||||||
|
{option.label}
|
||||||
|
</Badge>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</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 === "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]}
|
||||||
@ -211,7 +360,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<!-- <div class="flex-1 min-h-0 overflow-y-auto p-2 space-y-6"> -->
|
|
||||||
<div class="p-2 space-y-6">
|
<div class="p-2 space-y-6">
|
||||||
{#each formFields as group}
|
{#each formFields as group}
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
|
|||||||
@ -15,14 +15,11 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col p-2 gap-4 h-full w-full">
|
<div class="flex flex-col p-2 h-full w-full">
|
||||||
<TopbarWrapper actions={actions} title={title}/>
|
<TopbarWrapper actions={actions} title={title}/>
|
||||||
<!-- <div class="flex-1 min-h-0 flex flex-col gap-4"> -->
|
|
||||||
<div class="flex-1 min-h-0 overflow-y-auto p-2">
|
<div class="flex-1 min-h-0 overflow-y-auto p-2">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="mt-auto flex justify-end items-center pt-2"> -->
|
|
||||||
<!-- <div class="flex justify-end items-center pt-2"> -->
|
|
||||||
<div class="shrink-0 border-t pt-2 flex justify-end items-center">
|
<div class="shrink-0 border-t pt-2 flex justify-end items-center">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
let props = $props();
|
let props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-8 flex justify-between items-center">
|
<div class="h-8 flex justify-between items-center pb-2">
|
||||||
<h3 class="text-2xl font-semibold">
|
<h3 class="text-2xl font-semibold">
|
||||||
{props.title}
|
{props.title}
|
||||||
{#if props.subtitle}
|
{#if props.subtitle}
|
||||||
|
|||||||
16
src/lib/components/ui/tabs/index.js
Normal file
16
src/lib/components/ui/tabs/index.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Root from "./tabs.svelte";
|
||||||
|
import Content from "./tabs-content.svelte";
|
||||||
|
import List from "./tabs-list.svelte";
|
||||||
|
import Trigger from "./tabs-trigger.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Content,
|
||||||
|
List,
|
||||||
|
Trigger,
|
||||||
|
//
|
||||||
|
Root as Tabs,
|
||||||
|
Content as TabsContent,
|
||||||
|
List as TabsList,
|
||||||
|
Trigger as TabsTrigger,
|
||||||
|
};
|
||||||
17
src/lib/components/ui/tabs/tabs-content.svelte
Normal file
17
src/lib/components/ui/tabs/tabs-content.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
bind:ref
|
||||||
|
data-slot="tabs-content"
|
||||||
|
class={cn("flex-1 outline-none", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
20
src/lib/components/ui/tabs/tabs-list.svelte
Normal file
20
src/lib/components/ui/tabs/tabs-list.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<TabsPrimitive.List
|
||||||
|
bind:ref
|
||||||
|
data-slot="tabs-list"
|
||||||
|
class={cn(
|
||||||
|
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
20
src/lib/components/ui/tabs/tabs-trigger.svelte
Normal file
20
src/lib/components/ui/tabs/tabs-trigger.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
bind:ref
|
||||||
|
data-slot="tabs-trigger"
|
||||||
|
class={cn(
|
||||||
|
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
19
src/lib/components/ui/tabs/tabs.svelte
Normal file
19
src/lib/components/ui/tabs/tabs.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
value = $bindable(""),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<TabsPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
bind:value
|
||||||
|
data-slot="tabs"
|
||||||
|
class={cn("flex flex-col gap-2", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
Loading…
x
Reference in New Issue
Block a user