build: add vscode launch config and tailwind config

Add launch configuration for Chrome debugging in VSCode and set up TailwindCSS configuration with animations and theme customization
This commit is contained in:
Khafid Prayoga
2025-08-26 17:05:08 +07:00
parent 153c171a3b
commit 04b87733d5
12 changed files with 867 additions and 384 deletions
+38 -40
View File
@@ -21,9 +21,9 @@
--muted: 210 25% 95%;
--muted-foreground: 210 15% 50%;
/* Accent - Professional Blue */
--accent: 210 100% 50%;
--accent-foreground: 0 0% 100%;
/* Accent - Neutral Gray */
--accent: 210 40% 96%;
--accent-foreground: 222.2 84% 4.9%;
--destructive: 0 75% 55%;
--destructive-foreground: 0 0% 100%;
--border: 210 20% 88%;
@@ -67,45 +67,43 @@
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
/* .dark { */
/* --background: 210 25% 8%; */
/* --foreground: 210 20% 95%; */
/* --card: 210 25% 10%; */
/* --card-foreground: 210 20% 95%; */
/* --popover: 210 25% 10%; */
/* --popover-foreground: 210 20% 95%; */
/* --primary: 150 75% 45%; */
/* --primary-foreground: 0 0% 100%; */
/* --primary-hover: 150 75% 50%; */
/* --secondary: 210 25% 15%; */
/* --secondary-foreground: 210 20% 90%; */
/* --muted: 210 25% 15%; */
/* --muted-foreground: 210 15% 65%; */
/* --accent: 210 100% 55%; */
/* --accent-foreground: 0 0% 100%; */
/* --destructive: 0 75% 60%; */
/* --destructive-foreground: 0 0% 100%; */
/* --border: 210 25% 20%; */
/* --input: 210 25% 15%; */
/* --ring: 150 75% 45%; */
/* --success: 150 75% 50%; */
/* --warning: 45 95% 65%; */
/* --info: 210 100% 60%; */
/* --gradient-primary: linear-gradient(135deg, hsl(150 75% 45%), hsl(150 75% 55%)); */
/* --gradient-medical: linear-gradient(135deg, hsl(150 75% 45%), hsl(210 100% 55%)); */
/* --gradient-subtle: linear-gradient(180deg, hsl(210 25% 8%), hsl(210 25% 12%)); */
/* --sidebar-background: 240 5.9% 10%; */
/* --sidebar-foreground: 240 4.8% 95.9%; */
/* --sidebar-primary: 224.3 76.3% 48%; */
/* --sidebar-primary-foreground: 0 0% 100%; */
/* --sidebar-accent: 240 3.7% 15.9%; */
/* --sidebar-accent-foreground: 240 4.8% 95.9%; */
/* --sidebar-border: 240 3.7% 15.9%; */
/* --sidebar-ring: 217.2 91.2% 59.8%; */
}
/* .dark { */
/* --background: 210 25% 8%; */
/* --foreground: 210 20% 95%; */
/* --card: 210 25% 10%; */
/* --card-foreground: 210 20% 95%; */
/* --popover: 210 25% 10%; */
/* --popover-foreground: 210 20% 95%; */
/* --primary: 150 75% 45%; */
/* --primary-foreground: 0 0% 100%; */
/* --primary-hover: 150 75% 50%; */
/* --secondary: 210 25% 15%; */
/* --secondary-foreground: 210 20% 90%; */
/* --muted: 210 25% 15%; */
/* --muted-foreground: 210 15% 65%; */
/* --accent: 210 100% 55%; */
/* --accent-foreground: 0 0% 100%; */
/* --destructive: 0 75% 60%; */
/* --destructive-foreground: 0 0% 100%; */
/* --border: 210 25% 20%; */
/* --input: 210 25% 15%; */
/* --ring: 150 75% 45%; */
/* --success: 150 75% 50%; */
/* --warning: 45 95% 65%; */
/* --info: 210 100% 60%; */
/* --gradient-primary: linear-gradient(135deg, hsl(150 75% 45%), hsl(150 75% 55%)); */
/* --gradient-medical: linear-gradient(135deg, hsl(150 75% 45%), hsl(210 100% 55%)); */
/* --gradient-subtle: linear-gradient(180deg, hsl(210 25% 8%), hsl(210 25% 12%)); */
/* --sidebar-background: 240 5.9% 10%; */
/* --sidebar-foreground: 240 4.8% 95.9%; */
/* --sidebar-primary: 224.3 76.3% 48%; */
/* --sidebar-primary-foreground: 0 0% 100%; */
/* --sidebar-accent: 240 3.7% 15.9%; */
/* --sidebar-accent-foreground: 240 4.8% 95.9%; */
/* --sidebar-border: 240 3.7% 15.9%; */
/* --sidebar-ring: 217.2 91.2% 59.8%; */
/* } */
/* Keyframes for Animations */
@keyframes accordion-down {
from {
+34 -243
View File
@@ -1,260 +1,51 @@
<script setup lang="ts">
import type { DateValue } from '@internationalized/date'
import type { DateRange } from 'radix-vue'
import type { Grid } from 'radix-vue/date'
import type { Ref } from 'vue'
import { CalendarDate } from '@internationalized/date'
import { Calendar as CalendarIcon, ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { RangeCalendarRoot, useDateFormatter } from 'radix-vue'
import { createMonth, toDate } from 'radix-vue/date'
import { buttonVariants } from '~/components/pub/ui/button'
import {
CalendarDate,
DateFormatter,
getLocalTimeZone,
} from '@internationalized/date'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { cn } from '~/lib/utils'
// Props untuk kustomisasi
interface Props {
modelValue?: DateRange
placeholder?: string
class?: string
}
const props = withDefaults(defineProps<Props>(), {
placeholder: 'Pick a date range',
class: '',
const df = new DateFormatter('en-US', {
dateStyle: 'medium',
})
const emit = defineEmits<{
'update:modelValue': [value: DateRange]
}>()
const value = ref(props.modelValue || {
const value = ref({
start: new CalendarDate(2022, 1, 20),
end: new CalendarDate(2022, 2, 9),
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
}) as Ref<DateRange>
watch(value, (newValue) => {
emit('update:modelValue', newValue)
}, { deep: true })
const locale = ref('en-US')
const formatter = useDateFormatter(locale.value)
const placeholder = ref(value.value.start) as Ref<DateValue>
const secondMonthPlaceholder = ref(value.value.end) as Ref<DateValue>
// Create independent month grids
const firstMonth = ref(
createMonth({
dateObj: placeholder.value,
locale: locale.value,
fixedWeeks: true,
weekStartsOn: 0,
}),
) as Ref<Grid<DateValue>>
const secondMonth = ref(
createMonth({
dateObj: secondMonthPlaceholder.value,
locale: locale.value,
fixedWeeks: true,
weekStartsOn: 0,
}),
) as Ref<Grid<DateValue>>
// Function to update months independently
function updateMonth(reference: 'first' | 'second', months: number) {
if (reference === 'first') {
placeholder.value = placeholder.value.add({ months })
} else {
secondMonthPlaceholder.value = secondMonthPlaceholder.value.add({ months })
}
}
// Watch first month placeholder
watch(placeholder, (_placeholder) => {
firstMonth.value = createMonth({
dateObj: _placeholder,
weekStartsOn: 0,
fixedWeeks: true,
locale: locale.value,
})
})
// Watch second month placeholder
watch(secondMonthPlaceholder, (_secondMonthPlaceholder) => {
secondMonth.value = createMonth({
dateObj: _secondMonthPlaceholder,
weekStartsOn: 0,
fixedWeeks: true,
locale: locale.value,
})
})
</script>
<template>
<div :class="cn('w-full', props.class)">
<Popover>
<PopoverTrigger as-child>
<Button variant="outline" :class="cn(
'w-full justify-start text-left font-normal',
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!value && 'text-muted-foreground',
)">
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start">
<template v-if="value.end">
{{
formatter.custom(toDate(value.start), {
dateStyle: 'medium',
})
}}
-
{{
formatter.custom(toDate(value.end), {
dateStyle: 'medium',
})
}}
</template>
<template v-else>
{{
formatter.custom(toDate(value.start), {
dateStyle: 'medium',
})
}}
</template>
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start">
<template v-if="value.end">
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
</template>
<template v-else>
{{ props.placeholder }}
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
</template>
</Button>
</PopoverTrigger>
<PopoverContent
class="w-auto p-0 rounded-xl shadow-lg border bg-white dark:bg-neutral-900 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
align="start">
<RangeCalendarRoot v-slot="{ weekDays }" v-model="value" v-model:placeholder="placeholder" class="p-4">
<div class="flex flex-col gap-6 sm:flex-row sm:gap-8">
<!-- First Month -->
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<Button :class="cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)" @click="updateMonth('first', -1)">
<ChevronLeft class="h-4 w-4 text-primary" />
</Button>
<div class="text-sm font-medium">
{{
formatter.fullMonthAndYear(
toDate(firstMonth.value),
)
}}
</div>
<Button :class="cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)" @click="updateMonth('first', 1)">
<ChevronRight class="h-4 w-4 text-primary" />
</Button>
</div>
<RangeCalendarGrid>
<RangeCalendarGridHead>
<RangeCalendarGridRow>
<RangeCalendarHeadCell v-for="day in weekDays" :key="day" class="w-full">
{{ day }}
</RangeCalendarHeadCell>
</RangeCalendarGridRow>
</RangeCalendarGridHead>
<RangeCalendarGridBody class="mt-2">
<RangeCalendarGridRow v-for="(weekDates, index) in firstMonth.rows" :key="`weekDate-${index}`"
class="w-full">
<RangeCalendarCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
<RangeCalendarCellTrigger :day="weekDate" :month="firstMonth.value" :class="cn(
'h-9 w-9 p-0 text-sm rounded-md transition-colors flex items-center justify-center',
// tanggal bulan lain (past/next month)
'data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50',
// tanggal terpilih (start / end)
'data-[selected]:bg-green-600 data-[selected]:text-white',
// tanggal dalam rentang
'data-[in-range]:bg-green-500 data-[in-range]:text-white',
// rounded pill untuk range
'data-[selection-start]:rounded-l-full data-[selection-end]:rounded-r-full',
// hover
'hover:bg-accent hover:text-accent-foreground'
)" />
</RangeCalendarCell>
</RangeCalendarGridRow>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</div>
<!-- Second Month -->
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<Button :class="cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)" @click="updateMonth('second', -1)">
<ChevronLeft class="h-4 w-4 text-primary" />
</Button>
<div class="text-sm font-medium">
{{
formatter.fullMonthAndYear(
toDate(secondMonth.value),
)
}}
</div>
<Button :class="cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)" @click="updateMonth('second', 1)">
<ChevronRight class="h-4 w-4 text-primary" />
</Button>
</div>
<RangeCalendarGrid>
<RangeCalendarGridHead>
<RangeCalendarGridRow>
<RangeCalendarHeadCell v-for="day in weekDays" :key="day" class="w-full">
{{ day }}
</RangeCalendarHeadCell>
</RangeCalendarGridRow>
</RangeCalendarGridHead>
<RangeCalendarGridBody class="mt-2">
<RangeCalendarGridRow v-for="(weekDates, index) in secondMonth.rows" :key="`weekDate-${index}`"
class="w-full">
<RangeCalendarCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
<RangeCalendarCellTrigger :day="weekDate" :month="secondMonth.value" :class="cn(
'h-9 w-9 p-0 text-sm rounded-md transition-colors flex items-center justify-center',
// tanggal bulan lain (past/next month)
'data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50',
// tanggal terpilih (start / end)
'data-[selected]:bg-green-600 data-[selected]:text-white',
// tanggal dalam rentang
'data-[in-range]:bg-green-500 data-[in-range]:text-white',
// rounded pill untuk range
'data-[selection-start]:rounded-l-full data-[selection-end]:rounded-r-full',
// hover
'hover:bg-accent hover:text-accent-foreground'
)" />
</RangeCalendarCell>
</RangeCalendarGridRow>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</div>
</div>
</RangeCalendarRoot>
</PopoverContent>
</Popover>
</div>
</template>
<template v-else>
Pick a date
</template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<RangeCalendar v-model="value" initial-focus :number-of-months="2" @update:start-value="(startDate) => value.start = startDate" />
</PopoverContent>
</Popover>
</template>
+1
View File
@@ -125,6 +125,7 @@ const activeTabFilter = computed({
<!-- Search Input -->
<AppSatusehatPicker/>
<div class="relative w-full max-w-sm">
<Dialog>
+1 -2
View File
@@ -1,5 +1,4 @@
import type { ClassValue } from 'clsx'
import { clsx } from 'clsx'
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {