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-accent-foreground: 240 5.9% 10%;
|
||||||
--sidebar-border: 220 13% 91%;
|
--sidebar-border: 220 13% 91%;
|
||||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
/* .dark { */
|
}
|
||||||
/* --background: 210 25% 8%; */
|
|
||||||
/* --foreground: 210 20% 95%; */
|
.dark {
|
||||||
/* --card: 210 25% 10%; */
|
--background: 210 25% 8%;
|
||||||
/* --card-foreground: 210 20% 95%; */
|
--foreground: 210 20% 95%;
|
||||||
/* --popover: 210 25% 10%; */
|
--card: 210 25% 10%;
|
||||||
/* --popover-foreground: 210 20% 95%; */
|
--card-foreground: 210 20% 95%;
|
||||||
/* --primary: 150 75% 45%; */
|
--popover: 210 25% 10%;
|
||||||
/* --primary-foreground: 0 0% 100%; */
|
--popover-foreground: 210 20% 95%;
|
||||||
/* --primary-hover: 150 75% 50%; */
|
--primary: 150 75% 45%;
|
||||||
/* --secondary: 210 25% 15%; */
|
--primary-foreground: 0 0% 100%;
|
||||||
/* --secondary-foreground: 210 20% 90%; */
|
--primary-hover: 150 75% 50%;
|
||||||
/* --muted: 210 25% 15%; */
|
--secondary: 210 25% 15%;
|
||||||
/* --muted-foreground: 210 15% 65%; */
|
--secondary-foreground: 210 20% 90%;
|
||||||
/* --accent: 210 100% 55%; */
|
--muted: 210 25% 15%;
|
||||||
/* --accent-foreground: 0 0% 100%; */
|
--muted-foreground: 210 15% 65%;
|
||||||
/* --destructive: 0 75% 60%; */
|
--accent: 210 100% 55%;
|
||||||
/* --destructive-foreground: 0 0% 100%; */
|
--accent-foreground: 0 0% 100%;
|
||||||
/* --border: 210 25% 20%; */
|
--destructive: 0 75% 60%;
|
||||||
/* --input: 210 25% 15%; */
|
--destructive-foreground: 0 0% 100%;
|
||||||
/* --ring: 150 75% 45%; */
|
--border: 210 25% 20%;
|
||||||
/* --success: 150 75% 50%; */
|
--input: 210 25% 15%;
|
||||||
/* --warning: 45 95% 65%; */
|
--ring: 150 75% 45%;
|
||||||
/* --info: 210 100% 60%; */
|
--success: 150 75% 50%;
|
||||||
/* --gradient-primary: linear-gradient(135deg, hsl(150 75% 45%), hsl(150 75% 55%)); */
|
--warning: 45 95% 65%;
|
||||||
/* --gradient-medical: linear-gradient(135deg, hsl(150 75% 45%), hsl(210 100% 55%)); */
|
--info: 210 100% 60%;
|
||||||
/* --gradient-subtle: linear-gradient(180deg, hsl(210 25% 8%), hsl(210 25% 12%)); */
|
--gradient-primary: linear-gradient(135deg, hsl(150 75% 45%), hsl(150 75% 55%));
|
||||||
/* --sidebar-background: 240 5.9% 10%; */
|
--gradient-medical: linear-gradient(135deg, hsl(150 75% 45%), hsl(210 100% 55%));
|
||||||
/* --sidebar-foreground: 240 4.8% 95.9%; */
|
--gradient-subtle: linear-gradient(180deg, hsl(210 25% 8%), hsl(210 25% 12%));
|
||||||
/* --sidebar-primary: 224.3 76.3% 48%; */
|
--sidebar-background: 240 5.9% 10%;
|
||||||
/* --sidebar-primary-foreground: 0 0% 100%; */
|
--sidebar-foreground: 240 4.8% 95.9%;
|
||||||
/* --sidebar-accent: 240 3.7% 15.9%; */
|
--sidebar-primary: 224.3 76.3% 48%;
|
||||||
/* --sidebar-accent-foreground: 240 4.8% 95.9%; */
|
--sidebar-primary-foreground: 0 0% 100%;
|
||||||
/* --sidebar-border: 240 3.7% 15.9%; */
|
--sidebar-accent: 240 3.7% 15.9%;
|
||||||
/* --sidebar-ring: 217.2 91.2% 59.8%; */
|
--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 for Animations */
|
||||||
@@ -330,7 +332,8 @@ body {
|
|||||||
|
|
||||||
/* Form Error Styling */
|
/* Form Error Styling */
|
||||||
.field-error-info {
|
.field-error-info {
|
||||||
@apply text-xs ml-1;
|
font-size: 0.75rem;
|
||||||
|
margin-left: 0.25rem;
|
||||||
color: hsl(var(--destructive));
|
color: hsl(var(--destructive));
|
||||||
/* font-size: 0.875rem; */
|
/* font-size: 0.875rem; */
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
@@ -338,3 +341,28 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* .rounded-md { border-radius: var */
|
/* .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">
|
<div class="mt-4 flex flex-wrap items-center justify-between gap-2">
|
||||||
<h2 class="text-2xl font-bold tracking-tight">Dashboard SIMRS</h2>
|
<h2 class="text-2xl font-bold tracking-tight">Dashboard SIMRS</h2>
|
||||||
<div class="flex items-center gap-4 space-x-2">
|
<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">
|
<Button class="bg-primary p-2 text-white" size="lg">
|
||||||
<Icon name="i-lucide-refresh-ccw" class="h-6 w-6" />
|
<Icon name="i-lucide-refresh-ccw" class="h-6 w-6" />
|
||||||
Sinkronisasi
|
Sinkronisasi
|
||||||
@@ -121,10 +121,10 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<main class="my-6 flex flex-1 flex-col gap-4 md:gap-8">
|
<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" />
|
<PubBaseSummaryCard v-for="card in summaryData" :key="card.title" :stat="card" />
|
||||||
</div>
|
</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">
|
<Card v-for="n in 3" :key="n">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Recent Sales</CardTitle>
|
<CardTitle>Recent Sales</CardTitle>
|
||||||
@@ -143,7 +143,7 @@ onMounted(() => {
|
|||||||
<p class="text-sm font-medium leading-none">
|
<p class="text-sm font-medium leading-none">
|
||||||
{{ recentSales.name }}
|
{{ recentSales.name }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-sm text-muted-foreground">
|
||||||
{{ recentSales.email }}
|
{{ recentSales.email }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -156,19 +156,19 @@ onMounted(() => {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<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>
|
<h2 class="text-xl font-semibold tracking-tight">Aksi Cepat</h2>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</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
|
<Card
|
||||||
v-for="item in linkItems"
|
v-for="item in linkItems"
|
||||||
:key="item.title"
|
:key="item.title"
|
||||||
class="border-primary hover:bg-primary my-2 h-32 border transition-colors duration-200 hover:bg-gray-200"
|
class="my-2 h-32 border border-primary transition-colors duration-200 hover:bg-gray-200 hover:bg-primary"
|
||||||
>
|
>
|
||||||
<NuxtLink :to="item.link">
|
<NuxtLink :to="item.link">
|
||||||
<CardContent class="my-2 grid h-full grid-rows-2 place-items-center">
|
<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>
|
<h1>{{ item.title }}</h1>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import ThemeToggle from "./ThemeToggle.vue"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
function setLinks() {
|
function setLinks() {
|
||||||
@@ -50,9 +52,10 @@ watch(
|
|||||||
<Separator orientation="vertical" class="h-4" />
|
<Separator orientation="vertical" class="h-4" />
|
||||||
<PubBaseBreadcrumb :links="links" />
|
<PubBaseBreadcrumb :links="links" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-auto">
|
<div class="ml-auto flex items-center gap-2">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</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;
|
margin-right: auto;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
padding-bottom: 5rem;
|
padding-bottom: 5rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf-container > *,
|
.cf-container > *,
|
||||||
@@ -56,9 +58,9 @@ const contentContent = computed(() => {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding: 0.75rem; /* p-3 */
|
padding: 0.75rem; /* p-3 */
|
||||||
padding-bottom: 5rem; /* pb-20 */
|
padding-bottom: 5rem; /* pb-20 */
|
||||||
border-width: 1px;
|
background-color: hsl(var(--background));
|
||||||
background-color: white !important;
|
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
border-color: rgb(226 232 240); /* slate-200 */
|
border-color: rgb(226 232 240); /* slate-200 */
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
@@ -78,18 +80,58 @@ const contentContent = computed(() => {
|
|||||||
.cf-frame-width {
|
.cf-frame-width {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
background-color: white;
|
background-color: hsl(var(--background));
|
||||||
border: 1px solid rgb(226 232 240);
|
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf-frame {
|
.cf-frame {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding: 0.75rem;
|
padding: 1rem;
|
||||||
background-color: white;
|
background-color: hsl(var(--background));
|
||||||
border-radius: 0.375rem;
|
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);
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user