Merge branch 'dev' of github.com:dikstub-rssa/simrs-fe into feat/patient-63

This commit is contained in:
Khafid Prayoga
2025-10-13 13:13:54 +07:00
166 changed files with 3957 additions and 768 deletions
@@ -1,11 +1,12 @@
<script setup lang="ts">
import type { SelectItem } from '~/components/pub/my-ui/form/select.vue'
import { cn } from '~/lib/utils'
import { type Item } from './index'
const props = defineProps<{
id: string
id?: string
modelValue?: string
items: SelectItem[]
items: Item[]
placeholder?: string
searchPlaceholder?: string
emptyMessage?: string
@@ -22,12 +23,17 @@ const open = ref(false)
const selectedItem = computed(() => props.items.find((item) => item.value === props.modelValue))
const displayText = computed(() => {
console.log(selectedItem)
if (selectedItem.value?.label) {
return selectedItem.value.label
}
return props.placeholder || 'Pilih item'
})
watch(props, () => {
console.log(props.modelValue)
})
const searchableItems = computed(() => {
const itemsWithSearch = props.items.map((item) => ({
...item,
@@ -49,7 +55,7 @@ const searchableItems = computed(() => {
})
})
function onSelect(item: SelectItem) {
function onSelect(item: Item) {
emit('update:modelValue', item.value)
open.value = false
}
@@ -68,10 +74,10 @@ function onSelect(item: SelectItem) {
:aria-describedby="`${props.id}-search`"
:class="
cn(
'w-full justify-between rounded-md border px-3 py-2 text-sm font-normal focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white',
'h-8 w-full justify-between rounded-md border px-3 font-normal focus:outline-none focus:ring-1 focus:ring-black dark:!border-slate-400 dark:focus:ring-white md:text-xs 2xl:h-9 2xl:text-sm',
{
'cursor-not-allowed border-gray-300 bg-gray-100 text-gray-500 opacity-50': props.isDisabled,
'border-gray-400 bg-white text-black hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700':
'border-gray-400 bg-white text-black hover:bg-gray-50 dark:border-gray-600 dark:bg-slate-950 dark:text-white dark:hover:bg-gray-700':
!props.isDisabled,
'text-gray-400 dark:text-gray-500': !modelValue && !props.isDisabled,
},
@@ -116,7 +122,7 @@ function onSelect(item: SelectItem) {
:value="item.searchValue"
:class="
cn(
'flex w-full cursor-pointer items-center justify-between rounded-sm px-2 py-1.5 text-sm',
'flex w-full cursor-pointer items-center justify-between rounded-sm px-2 py-1.5 md:text-xs xl:text-sm',
'text-black focus:outline-none dark:text-white',
'hover:bg-primary hover:text-white focus:bg-primary focus:text-white',
'data-[highlighted]:bg-primary data-[highlighted]:text-white',
@@ -0,0 +1,23 @@
export interface Item {
value: string
label: string
code?: string
priority?: number
}
export function recStrToItem(input: Record<string, string>): Item[] {
const items: Item[] = []
let idx = 0;
for (const key in input) {
if (input.hasOwnProperty(key)) {
items.push({
value: key || ('unknown-' + idx),
label: input[key] || ('unknown-' + idx),
})
}
idx++
}
return items
}
export { default as Combobox } from './combobox.vue'
@@ -19,7 +19,7 @@ function changeTab(value: string) {
<template>
<!-- Tabs -->
<div class="mt-4 flex flex-wrap gap-2 rounded-md border bg-white p-4 shadow-sm">
<div class="mt-4 flex flex-wrap gap-2 rounded-md border bg-white dark:bg-neutral-950 p-4 shadow-sm">
<Button
v-for="tab in data"
:key="tab.value"
@@ -9,19 +9,7 @@ const props = defineProps<{
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
const activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Detail',
@@ -38,6 +26,18 @@ const linkItems: LinkItem[] = [
icon: 'i-lucide-pencil',
},
]
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
</script>
<template>
@@ -46,22 +46,29 @@ const linkItems: LinkItem[] = [
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
>
<Icon name="i-lucide-chevrons-up-down" class="ml-auto size-4" />
<Icon
name="i-lucide-chevrons-up-down"
class="ml-auto size-4"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg bg-white" align="end">
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
align="end"
>
<DropdownMenuGroup>
<DropdownMenuItem
v-for="item in linkItems"
:key="item.label"
v-slot="{ active }"
class="hover:bg-gray-100"
class="hover:bg-gray-100 dark:hover:bg-slate-700"
@click="item.onClick"
@mouseenter="activeKey = item.label"
@mouseleave="activeKey = null"
>
<Icon :name="item.icon" />
<span :class="active ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
<Icon :name="item.icon ?? ''" />
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
@@ -9,25 +9,7 @@ const props = defineProps<{
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
const activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Detail',
@@ -58,6 +40,24 @@ const linkItems: LinkItem[] = [
icon: 'i-lucide-trash',
},
]
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
</script>
<template>
@@ -66,22 +66,29 @@ const linkItems: LinkItem[] = [
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
>
<Icon name="i-lucide-chevrons-up-down" class="ml-auto size-4" />
<Icon
name="i-lucide-chevrons-up-down"
class="ml-auto size-4"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg bg-white" align="end">
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
align="end"
>
<DropdownMenuGroup>
<DropdownMenuItem
v-for="item in linkItems"
:key="item.label"
v-slot="{ active }"
class="hover:bg-gray-100"
class="hover:bg-gray-100 dark:hover:bg-slate-700"
@click="item.onClick"
@mouseenter="activeKey = item.label"
@mouseleave="activeKey = null"
>
<Icon :name="item.icon" />
<span :class="active ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
<Icon :name="item.icon ?? ''" />
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
@@ -9,25 +9,7 @@ const props = defineProps<{
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
const activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Detail',
@@ -51,6 +33,24 @@ const linkItems: LinkItem[] = [
icon: 'i-lucide-trash',
},
]
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
</script>
<template>
@@ -59,22 +59,29 @@ const linkItems: LinkItem[] = [
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
>
<Icon name="i-lucide-chevrons-up-down" class="ml-auto size-4" />
<Icon
name="i-lucide-chevrons-up-down"
class="ml-auto size-4"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg bg-white" align="end">
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
align="end"
>
<DropdownMenuGroup>
<DropdownMenuItem
v-for="item in linkItems"
:key="item.label"
v-slot="{ active }"
class="hover:bg-gray-100"
class="hover:bg-gray-100 dark:hover:bg-slate-700"
@click="item.onClick"
@mouseenter="activeKey = item.label"
@mouseleave="activeKey = null"
>
<Icon :name="item.icon" />
<span :class="active ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
<Icon :name="item.icon ?? ''" />
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
@@ -9,31 +9,7 @@ const props = defineProps<{
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function process() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showProcess
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
const activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Proses',
@@ -64,6 +40,30 @@ const linkItems: LinkItem[] = [
icon: 'i-lucide-trash',
},
]
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function process() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showProcess
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
</script>
<template>
@@ -72,22 +72,29 @@ const linkItems: LinkItem[] = [
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
>
<Icon name="i-lucide-chevrons-up-down" class="ml-auto size-4" />
<Icon
name="i-lucide-chevrons-up-down"
class="ml-auto size-4"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg bg-white" align="end">
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
align="end"
>
<DropdownMenuGroup>
<DropdownMenuItem
v-for="item in linkItems"
:key="item.label"
v-slot="{ active }"
class="hover:bg-gray-100"
class="hover:bg-gray-100 dark:hover:bg-slate-700"
@click="item.onClick"
@mouseenter="activeKey = item.label"
@mouseleave="activeKey = null"
>
<Icon :name="item.icon" />
<span :class="active ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
<Icon :name="item.icon ?? ''" />
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
@@ -14,19 +14,7 @@ const props = withDefaults(defineProps<Props>(), {
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
const activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Edit',
@@ -43,6 +31,18 @@ const linkItems: LinkItem[] = [
icon: 'i-lucide-trash',
},
]
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
</script>
<template>
@@ -51,22 +51,29 @@ const linkItems: LinkItem[] = [
<DropdownMenuTrigger as-child>
<SidebarMenuButton
:size="size"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
>
<Icon name="i-lucide-chevrons-up-down" class="ml-auto size-4" />
<Icon
name="i-lucide-chevrons-up-down"
class="ml-auto size-4"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg bg-white" align="end">
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
align="end"
>
<DropdownMenuGroup>
<DropdownMenuItem
v-for="item in linkItems"
:key="item.label"
v-slot="{ active }"
class="hover:bg-gray-100"
class="hover:bg-gray-100 dark:hover:bg-slate-700"
@click="item.onClick"
@mouseenter="activeKey = item.label"
@mouseleave="activeKey = null"
>
<Icon :name="item.icon" />
<span :class="active ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
<Icon :name="item.icon ?? ''" />
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
+3 -3
View File
@@ -60,11 +60,11 @@ const settingClass = computed(() => {
'[&_.label]:md:w-44 [&_.label]:xl:w-52',
][getLabelSizeIdx(props.labelSize)]
} else {
cls += ' [&_.label]:pb-1 ';
cls += ' [&_.label]:pb-1 [&_.label]:!pt-0 ';
}
cls += ' [&:not(.preview)_.height-default]:pt-2 [&:not(.preview)_.height-default]:2xl:!pt-1.5 [&:not(.preview)_.height-compact]:!pt-1 '
cls += '[&_textarea]:text-xs [&_textarea]:xl:text-sm '
cls += '[&_label]:text-xs [&_label]:xl:text-sm'
cls += '[&_textarea]:text-xs [&_textarea]:2xl:!text-sm '
cls += '[&_label]:text-xs [&_label]:2xl:!text-sm'
return cls
})
</script>
+1 -1
View File
@@ -9,6 +9,6 @@ const props = defineProps({
<template>
<div :class="`field ${props.defaultClass} ${props.class}`">
<slot />
<div v-if="props.errMessage" class="mt-1 text-xs font-medium text-red-500">{{ props.errMessage }}</div>
<div v-if="props.errMessage" class="mt-1 md:text-xs 2xl:text-sm font-medium text-red-500">{{ props.errMessage }}</div>
</div>
</template>
+1 -1
View File
@@ -32,7 +32,7 @@ const settingClass = computed(() => {
<template>
<div :class="settingClass">
<label>
<label v-bind="$attrs">
<slot />
</label>
</div>
@@ -20,7 +20,12 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<template>
<ComboboxItem
v-bind="forwarded"
:class="cn('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm font-normal outline-none hover:bg-accent data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>*]:text-sm [&>*]:font-normal', props.class)"
:class="
cn(
'relative flex cursor-default select-none items-center rounded-sm bg-white px-2 py-1.5 text-sm font-normal text-black outline-none hover:bg-accent data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:bg-slate-800 dark:text-white dark:hover:bg-slate-700 [&>*]:text-sm [&>*]:font-normal',
props.class,
)
"
>
<slot />
</ComboboxItem>
@@ -20,11 +20,11 @@ function handleSelect(value: string) {
<div class="leaf-node min-w-max">
<CommandItem
:value="item.value"
class="flex items-center justify-between p-2 w-full text-sm font-normal hover:text-primary cursor-pointer rounded-md"
class="flex items-center justify-between p-2 w-full text-sm font-normal hover:text-primary cursor-pointer rounded-md bg-white dark:bg-transparent"
:class="{ 'pl-8': shouldAlign }"
@select="() => handleSelect(item.value)"
>
<span class="text-sm font-normal text-gray-400">{{ item.label }}</span>
<span class="text-sm font-normal text-gray-400 dark:text-gray-300">{{ item.label }}</span>
<Check
v-if="selectedValue === item.value"
class="w-4 h-4 text-primary ml-2 flex-shrink-0"
@@ -52,19 +52,27 @@ watch(isOpen, async (newValue) => {
<template>
<div class="tree-node min-w-max">
<Collapsible v-model:open="isOpen" class="w-full">
<Collapsible
v-model:open="isOpen"
class="w-full"
>
<!-- Node Header -->
<div class="flex items-center justify-start w-full p-2 rounded-md hover:bg-accent gap-2">
<div
class="flex w-full items-center justify-start gap-2 rounded-md bg-white p-2 hover:bg-accent dark:bg-transparent dark:hover:bg-slate-700"
>
<!-- Chevron Toggle Button -->
<CollapsibleTrigger as-child>
<Button
variant="ghost"
class="h-4 w-4 p-0 flex items-center justify-center"
class="flex h-4 w-4 items-center justify-center p-0"
>
<Loader2 v-if="isLoading" class="w-4 h-4 animate-spin text-muted-foreground" />
<Loader2
v-if="isLoading"
class="h-4 w-4 animate-spin text-muted-foreground"
/>
<ChevronRight
v-else
class="w-4 h-4 transition-transform duration-200 ease-in-out text-muted-foreground"
class="h-4 w-4 text-muted-foreground transition-transform duration-200 ease-in-out"
:class="{
'rotate-90': isChevronRotated,
}"
@@ -74,21 +82,24 @@ watch(isOpen, async (newValue) => {
<!-- Node Label -->
<span
class="text-sm font-normal cursor-pointer hover:text-primary flex-1 flex items-center justify-between"
class="flex flex-1 cursor-pointer items-center justify-between text-sm font-normal text-black hover:text-primary dark:text-white"
@click="handleLabelClick"
>
{{ item.label }}
<!-- Check Icon untuk selected state -->
<Check
v-if="selectedValue === item.value"
class="w-4 h-4 text-primary ml-2 flex-shrink-0"
class="ml-2 h-4 w-4 flex-shrink-0 text-primary"
/>
</span>
</div>
<!-- Children Container -->
<CollapsibleContent class="pl-6">
<div v-if="!hasChildren" class="text-sm text-muted-foreground p-2">
<div
v-if="!hasChildren"
class="p-2 text-sm text-muted-foreground"
>
{{ isLoading ? 'Memuat...' : 'Tidak ada data' }}
</div>
<TreeView
@@ -106,7 +117,7 @@ watch(isOpen, async (newValue) => {
<style scoped>
.tree-node {
@apply w-full;
width: 100%;
}
/* Animasi tambahan untuk smooth transition */
@@ -37,7 +37,7 @@ const filteredData = computed(() => {
// recursive filter
function filterTree(items: TreeItem[]): TreeItem[] {
return items
.map(item => {
.map((item) => {
const match = item.label.toLowerCase().includes(searchValue.value.toLowerCase())
let children: TreeItem[] | undefined = undefined
if (item.children) {
@@ -57,22 +57,28 @@ const filteredData = computed(() => {
<template>
<Popover v-model:open="open">
<PopoverTrigger as-child>
<Button variant="outline" role="combobox" class="w-full justify-between bg-white border-1 border-gray-400">
<Button
variant="outline"
role="combobox"
class="w-full justify-between border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
>
<span
class="font-normal text-muted-foreground" :class="cn(
'font-normal',
!modelValue && 'text-muted-foreground',
modelValue && 'text-black',
)"
class="font-normal text-muted-foreground"
:class="cn('font-normal', !modelValue && 'text-muted-foreground', modelValue && 'text-black')"
>
{{ selectedLabel }}
</span>
<ChevronsUpDown class="w-4 h-4 ml-2 opacity-50 shrink-0" />
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent class="min-w-full max-w-[350px] p-0">
<PopoverContent
class="min-w-full max-w-[350px] border border-slate-200 bg-white p-0 text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
>
<Command>
<CommandInput placeholder="Cari item..." v-model="searchValue" />
<CommandInput
placeholder="Cari item..."
v-model="searchValue"
/>
<CommandEmpty>Item tidak ditemukan.</CommandEmpty>
<CommandList class="max-h-[300px] overflow-x-auto overflow-y-auto">
<CommandGroup>
+2 -2
View File
@@ -4,7 +4,7 @@ import { cva } from 'class-variance-authority'
export { default as Button } from './Button.vue'
export const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md md:tex-xs xl:text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md md:text-xs 2xl:text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
@@ -19,7 +19,7 @@ export const buttonVariants = cva(
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'md:h8 xl:h-9 px-4 py-2',
default: 'md:h8 2xl:h-9 px-4 py-2',
xs: 'h-7 rounded px-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
+1 -1
View File
@@ -8,7 +8,7 @@ const props = defineProps<{
</script>
<template>
<div :class="cn('p-4 xl:p-5 2xl:p-6', props.class)">
<div :class="cn('p-4 2xl:p-5', props.class)">
<slot />
</div>
</template>
@@ -8,7 +8,7 @@ const props = defineProps<{
</script>
<template>
<p :class="cn('text-sm text-muted-foreground', props.class)">
<p :class="cn('text-xs 2xl:text-sm text-muted-foreground', props.class)">
<slot />
</p>
</template>
+1 -1
View File
@@ -8,7 +8,7 @@ const props = defineProps<{
</script>
<template>
<div :class="cn('flex items-center p-6 pt-0', props.class)">
<div :class="cn('flex items-center p-4 2xl:p-5 pt-0', props.class)">
<slot />
</div>
</template>
+1 -1
View File
@@ -21,7 +21,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<CheckboxRoot
v-bind="forwarded"
:class="
cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
cn('peer h-6 w-6 md:h-5 md:w-5 2xl:h-6 2xl:w-6 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
props.class)"
>
<CheckboxIndicator class="h-full w-full flex items-center justify-center text-current">
@@ -14,7 +14,7 @@ const { formDescriptionId } = useFormField()
<template>
<p
:id="formDescriptionId"
:class="cn('text-sm text-muted-foreground', props.class)"
:class="cn('md:text-xs 2xl:text-sm text-muted-foreground', props.class)"
>
<slot />
</p>
+1 -1
View File
@@ -24,7 +24,7 @@ const modelValue = useVModel(props, 'modelValue', emits, {
v-model="modelValue"
:class="
cn(
'border-input dark:bg-slate-950 ring-offset-background placeholder:text-muted-foreground flex h-8 xl:h-9 w-full rounded-md border border-gray-400 px-3 py-2 md:text-xs xl:text-sm file:border-0 file:bg-transparent md:file:text-xs xl:file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50',
'border-input dark:bg-slate-950 ring-offset-background placeholder:text-muted-foreground flex h-8 2xl:h-9 w-full rounded-md border border-gray-400 px-3 py-2 md:text-xs 2xl:text-sm file:border-0 file:bg-transparent md:file:text-xs xl:file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50',
props.class,
)
"
+1 -1
View File
@@ -19,7 +19,7 @@ const delegatedProps = computed(() => {
v-bind="delegatedProps"
:class="
cn(
'md:!text-xs xl:text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
'md:!text-xs 2xl:text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
props.class,
)
"
+5 -5
View File
@@ -30,7 +30,7 @@ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
<template>
<div
v-if="collapsible === 'none'"
:class="cn('bg-sidebar text-sidebar-foreground flex h-full w-[--sidebar-width] flex-col', props.class)"
:class="cn('bg-sidebar text-sidebar-foreground flex h-full w-[--sidebar-width] 2xl:w-[--xxl-sidebar-width] flex-col', props.class)"
v-bind="$attrs"
>
<slot />
@@ -64,7 +64,7 @@ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
<div
:class="
cn(
'relative h-svh w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear',
'relative h-svh w-[--sidebar-width] 2xl:w-[--xxl-sidebar-width] bg-transparent transition-[width] duration-200 ease-linear',
'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
@@ -76,10 +76,10 @@ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
<div
:class="
cn(
'fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex',
'fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] 2xl:w-[--xxl-sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex',
side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] group-data-[collapsible=offcanvas]:2xl:left-[calc(var(--xxl-sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] group-data-[collapsible=offcanvas]:2xl:right-[calc(var(--xxl-sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_1rem_+_2px)]'
@@ -15,7 +15,7 @@ const props = defineProps<PrimitiveProps & {
:as="as"
:as-child="asChild"
:class="cn(
'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 md:text-xs 2xl:text-sm font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
props.class)"
>
@@ -11,7 +11,7 @@ const props = defineProps<{
<div
data-sidebar="menu-badge"
:class="cn(
'absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none',
'absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 md:text-xs 2xl:text-sm font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none',
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
@@ -4,14 +4,19 @@ import type { HTMLAttributes } from 'vue'
import { Primitive } from 'radix-vue'
import { cn } from '~/lib/utils'
const props = withDefaults(defineProps<PrimitiveProps & {
size?: 'sm' | 'md'
isActive?: boolean
class?: HTMLAttributes['class']
}>(), {
as: 'a',
size: 'md',
})
const props = withDefaults(
defineProps<
PrimitiveProps & {
size?: 'sm' | 'md'
isActive?: boolean
class?: HTMLAttributes['class']
}
>(),
{
as: 'a',
size: 'md',
},
)
</script>
<template>
@@ -21,14 +26,16 @@ const props = withDefaults(defineProps<PrimitiveProps & {
:as-child="asChild"
:data-size="size"
:data-active="isActive"
:class="cn(
'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
'aria-[current=page]:bg-sidebar-accent aria-[current=page]:font-medium aria-[current=page]:text-sidebar-accent-foreground data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs',
size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden',
props.class,
)"
:class="
cn(
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 py-2 outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:whitespace-normal [&>span:last-child]:break-words [&>svg]:size-4 [&>svg]:shrink-0',
'aria-[current=page]:bg-sidebar-accent aria-[current=page]:text-sidebar-accent-foreground data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground aria-[current=page]:font-medium',
size === 'sm' && 'text-xs',
size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden',
props.class,
)
"
>
<slot />
</Primitive>
@@ -11,6 +11,7 @@ import {
SIDEBAR_COOKIE_NAME,
SIDEBAR_KEYBOARD_SHORTCUT,
SIDEBAR_WIDTH,
XXL_SIDEBAR_WIDTH,
SIDEBAR_WIDTH_ICON,
} from './utils'
@@ -87,6 +88,7 @@ provideSidebarContext({
<div
:style="{
'--sidebar-width': SIDEBAR_WIDTH,
'--xxl-sidebar-width': XXL_SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
}"
:class="
+4 -4
View File
@@ -28,7 +28,7 @@ export { default as SidebarTrigger } from './SidebarTrigger.vue'
export { useSidebar } from './utils'
export const sidebarMenuButtonVariants = cva(
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-[current=page]:bg-sidebar-accent aria-[current=page]:font-medium aria-[current=page]:text-sidebar-accent-foreground data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-[current=page]:bg-sidebar-accent aria-[current=page]:font-medium aria-[current=page]:text-sidebar-accent-foreground data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:whitespace-normal [&>span:last-child]:break-words [&>svg]:size-4 [&>svg]:shrink-0',
{
variants: {
variant: {
@@ -37,9 +37,9 @@ export const sidebarMenuButtonVariants = cva(
'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
},
size: {
default: 'h-8 text-sm',
sm: 'h-7 text-xs',
lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0',
default: 'h-auto text-sm py-2',
sm: 'h-auto text-xs py-1.5',
lg: 'h-auto text-sm group-data-[collapsible=icon]:!p-0',
},
},
defaultVariants: {
+2 -1
View File
@@ -3,7 +3,8 @@ import { createContext } from 'radix-vue'
export const SIDEBAR_COOKIE_NAME = 'sidebar:state'
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
export const SIDEBAR_WIDTH = '16rem'
export const SIDEBAR_WIDTH = '12rem'
export const XXL_SIDEBAR_WIDTH = '14rem'
export const SIDEBAR_WIDTH_MOBILE = '18rem'
export const SIDEBAR_WIDTH_ICON = '3rem'
export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
+1 -1
View File
@@ -9,7 +9,7 @@ const props = defineProps<{
<template>
<div class="relative w-full overflow-x-auto">
<table :class="cn('min-w-full table-fixed caption-bottom text-sm', props.class)">
<table :class="cn('min-w-full table-fixed caption-bottom md:text-xs 2xl:text-sm', props.class)">
<slot />
</table>
</div>
+1 -1
View File
@@ -8,7 +8,7 @@ const props = defineProps<{
</script>
<template>
<caption :class="cn('mt-4 text-sm text-muted-foreground', props.class)">
<caption :class="cn('mt-4 md:text-xs 2xl:text-sm text-muted-foreground', props.class)">
<slot />
</caption>
</template>
+1 -1
View File
@@ -11,7 +11,7 @@ const props = defineProps<{
<td
:class="
cn(
'p-4 align-middle [&:has([role=checkbox])]:pr-0',
'p2 2xl:p-3 align-middle md:text-xs 2xl:text-sm [&:has([role=checkbox])]:pr-0',
props.class,
)
"
+1 -1
View File
@@ -25,7 +25,7 @@ const delegatedProps = computed(() => {
<TableCell
:class="
cn(
'p-4 whitespace-nowrap align-middle text-sm text-foreground',
'p-4 whitespace-nowrap align-middle md:text-xs 2xl:text-sm text-foreground',
props.class,
)
"
+1 -1
View File
@@ -8,7 +8,7 @@ const props = defineProps<{
</script>
<template>
<th :class="cn('h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0', props.class)">
<th :class="cn('h-12 px-4 text-left md:text-xs 2xl:text-sm align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0', props.class)">
<slot />
</th>
</template>
+1 -1
View File
@@ -20,7 +20,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<TabsTrigger
v-bind="forwardedProps"
:class="cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 md:text-xs 2xl:text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
props.class,
)"
>
+1 -1
View File
@@ -20,5 +20,5 @@ const modelValue = useVModel(props, 'modelValue', emits, {
</script>
<template>
<textarea v-model="modelValue" :class="cn('flex min-h-20 w-full rounded-md border border-slate-400 bg-white dark:bg-slate-950 px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)" />
<textarea v-model="modelValue" :class="cn('flex min-h-20 w-full rounded-md border border-slate-400 bg-white dark:bg-slate-950 px-3 py-2 md:text-xs 2xl:text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)" />
</template>
+1 -1
View File
@@ -15,7 +15,7 @@ const delegatedProps = computed(() => {
</script>
<template>
<ToastAction v-bind="delegatedProps" :class="cn('inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive', props.class)">
<ToastAction v-bind="delegatedProps" :class="cn('inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 md:text-xs 2xl:text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive', props.class)">
<slot />
</ToastAction>
</template>
@@ -15,7 +15,7 @@ const delegatedProps = computed(() => {
</script>
<template>
<ToastDescription :class="cn('text-sm opacity-90', props.class)" v-bind="delegatedProps">
<ToastDescription :class="cn('md:text-xs 2xl:text-sm opacity-90', props.class)" v-bind="delegatedProps">
<slot />
</ToastDescription>
</template>
+1 -1
View File
@@ -15,7 +15,7 @@ const delegatedProps = computed(() => {
</script>
<template>
<ToastTitle v-bind="delegatedProps" :class="cn('text-sm font-semibold [&+div]:text-xs', props.class)">
<ToastTitle v-bind="delegatedProps" :class="cn('md:text-xs 2xl:text-sm font-semibold [&+div]:text-xs', props.class)">
<slot />
</ToastTitle>
</template>