Merge pull request #66 from dikstub-rssa/fe-public-features-46
Feat(public): Implement Dark Mode + Adjustment Padding Frame
This commit is contained in:
+64
-36
@@ -67,41 +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 */
|
||||
@@ -330,7 +332,8 @@ body {
|
||||
|
||||
/* Form Error Styling */
|
||||
.field-error-info {
|
||||
@apply text-xs ml-1;
|
||||
font-size: 0.75rem;
|
||||
margin-left: 0.25rem;
|
||||
color: hsl(var(--destructive));
|
||||
/* font-size: 0.875rem; */
|
||||
margin-top: 0.25rem;
|
||||
@@ -338,3 +341,28 @@ body {
|
||||
}
|
||||
|
||||
/* .rounded-md { border-radius: var */
|
||||
|
||||
/* Dashboard grid utility */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.dashboard-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.dashboard-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.dashboard-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 1.25rem;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ onMounted(() => {
|
||||
<div class="mt-4 flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="text-2xl font-bold tracking-tight">Dashboard SIMRS</h2>
|
||||
<div class="flex items-center gap-4 space-x-2">
|
||||
<div class="bg-primary rounded-xl border p-1 text-white">Status: Aktif</div>
|
||||
<div class="rounded-xl border bg-primary p-1 text-white">Status: Aktif</div>
|
||||
<Button class="bg-primary p-2 text-white" size="lg">
|
||||
<Icon name="i-lucide-refresh-ccw" class="h-6 w-6" />
|
||||
Sinkronisasi
|
||||
@@ -121,10 +121,10 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<main class="my-6 flex flex-1 flex-col gap-4 md:gap-8">
|
||||
<div class="grid gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-4">
|
||||
<div class="dashboard-grid">
|
||||
<PubBaseSummaryCard v-for="card in summaryData" :key="card.title" :stat="card" />
|
||||
</div>
|
||||
<div class="grid gap-4 md:gap-8 lg:grid-cols-1 xl:grid-cols-3">
|
||||
<div class="dashboard-grid">
|
||||
<Card v-for="n in 3" :key="n">
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Sales</CardTitle>
|
||||
@@ -143,7 +143,7 @@ onMounted(() => {
|
||||
<p class="text-sm font-medium leading-none">
|
||||
{{ recentSales.name }}
|
||||
</p>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ recentSales.email }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -156,19 +156,19 @@ onMounted(() => {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Icon name="i-lucide-activity" class="text-primary me-2 h-6 w-6" />
|
||||
<Icon name="i-lucide-activity" class="me-2 h-6 w-6 text-primary" />
|
||||
<h2 class="text-xl font-semibold tracking-tight">Aksi Cepat</h2>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="grid cursor-pointer gap-8 md:grid-cols-4 md:gap-8">
|
||||
<CardContent class="grid cursor-pointer gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-5">
|
||||
<Card
|
||||
v-for="item in linkItems"
|
||||
:key="item.title"
|
||||
class="border-primary hover:bg-primary my-2 h-32 border transition-colors duration-200 hover:bg-gray-200"
|
||||
>
|
||||
v-for="item in linkItems"
|
||||
:key="item.title"
|
||||
class="my-2 h-32 border border-primary transition-colors duration-200 hover:bg-gray-200 hover:bg-primary"
|
||||
>
|
||||
<NuxtLink :to="item.link">
|
||||
<CardContent class="my-2 grid h-full grid-rows-2 place-items-center">
|
||||
<Icon :name="item.icon" class="text-primary h-9 w-[60px]" />
|
||||
<Icon :name="item.icon" class="h-9 w-[60px] text-primary" />
|
||||
<h1>{{ item.title }}</h1>
|
||||
</CardContent>
|
||||
</NuxtLink>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import ThemeToggle from "./ThemeToggle.vue"
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
function setLinks() {
|
||||
@@ -50,9 +52,10 @@ watch(
|
||||
<Separator orientation="vertical" class="h-4" />
|
||||
<PubBaseBreadcrumb :links="links" />
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<slot />
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { useTheme } from '~/composables/useTheme'
|
||||
const { theme, toggleTheme } = useTheme()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
@click="toggleTheme"
|
||||
class="ml-2 rounded border px-2 py-1 text-sm"
|
||||
:title="theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'"
|
||||
>
|
||||
<span v-if="theme === 'dark'"><Icon name="i-lucide-moon" class="h-4 w-4 mt-1" /></span>
|
||||
<span v-else><Icon name="i-lucide-sun" class="h-4 w-4 mt-1" /></span>
|
||||
</button>
|
||||
</template>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ref, watchEffect } from 'vue'
|
||||
|
||||
const THEME_KEY = 'theme-mode'
|
||||
|
||||
export function useTheme() {
|
||||
const theme = ref<'light' | 'dark'>(getInitialTheme())
|
||||
|
||||
function getInitialTheme() {
|
||||
if (typeof window === 'undefined') return 'light'
|
||||
const persisted = localStorage.getItem(THEME_KEY)
|
||||
if (persisted === 'dark' || persisted === 'light') return persisted
|
||||
// fallback: system preference
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
function setTheme(newTheme: 'light' | 'dark') {
|
||||
theme.value = newTheme
|
||||
localStorage.setItem(THEME_KEY, newTheme)
|
||||
document.documentElement.classList.toggle('dark', newTheme === 'dark')
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
setTheme(theme.value === 'dark' ? 'light' : 'dark')
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
setTheme(theme.value)
|
||||
})
|
||||
|
||||
return { theme, toggleTheme }
|
||||
}
|
||||
+49
-7
@@ -45,6 +45,8 @@ const contentContent = computed(() => {
|
||||
margin-right: auto;
|
||||
border-radius: 0.375rem;
|
||||
padding-bottom: 5rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.cf-container > *,
|
||||
@@ -56,9 +58,9 @@ const contentContent = computed(() => {
|
||||
margin-right: auto;
|
||||
padding: 0.75rem; /* p-3 */
|
||||
padding-bottom: 5rem; /* pb-20 */
|
||||
border-width: 1px;
|
||||
background-color: white !important;
|
||||
background-color: hsl(var(--background));
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-color: rgb(226 232 240); /* slate-200 */
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@@ -78,18 +80,58 @@ const contentContent = computed(() => {
|
||||
.cf-frame-width {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background-color: white;
|
||||
border: 1px solid rgb(226 232 240);
|
||||
background-color: hsl(var(--background));
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid hsl(var(--border));
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
max-width: 100%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.cf-frame {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 0.75rem;
|
||||
background-color: white;
|
||||
padding: 1rem;
|
||||
background-color: hsl(var(--background));
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid rgb(226 232 240);
|
||||
border: 1px solid hsl(var(--border));
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.cf-container,
|
||||
.cf-container-lg,
|
||||
.cf-container-md,
|
||||
.cf-container-sm {
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.cf-frame {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.cf-frame-width {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.cf-container,
|
||||
.cf-container-lg,
|
||||
.cf-container-md,
|
||||
.cf-container-sm {
|
||||
padding-left: 3rem;
|
||||
padding-right: 3rem;
|
||||
}
|
||||
|
||||
.cf-frame {
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
.cf-frame-width {
|
||||
padding: 3rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user