Enhance layout
This commit is contained in:
parent
8c37b39eed
commit
8897df5036
23 changed files with 458 additions and 326 deletions
|
|
@ -3,12 +3,12 @@
|
|||
import { cn } from "@/shared/utils/cn";
|
||||
|
||||
const variants = {
|
||||
default: "bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300",
|
||||
default: "bg-black/5 dark:bg-white/10 text-text-muted",
|
||||
primary: "bg-primary/10 text-primary",
|
||||
success: "bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 border border-green-100 dark:border-green-800/30",
|
||||
warning: "bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-500 border border-yellow-100 dark:border-yellow-800/30",
|
||||
error: "bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 border border-red-100 dark:border-red-800/30",
|
||||
info: "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 border border-blue-100 dark:border-blue-800/30",
|
||||
success: "bg-green-500/10 text-green-600 dark:text-green-400",
|
||||
warning: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
|
||||
error: "bg-red-500/10 text-red-600 dark:text-red-400",
|
||||
info: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@
|
|||
import { cn } from "@/shared/utils/cn";
|
||||
|
||||
const variants = {
|
||||
primary: "bg-primary text-white hover:bg-primary-hover shadow-warm",
|
||||
secondary: "bg-surface border border-border text-text-main hover:bg-black/5 shadow-sm",
|
||||
outline: "border border-border text-text-main hover:bg-black/5",
|
||||
ghost: "text-text-muted hover:bg-black/5 hover:text-text-main",
|
||||
primary: "bg-gradient-to-b from-primary to-primary-hover text-white shadow-sm",
|
||||
secondary: "bg-white dark:bg-white/10 border border-black/10 dark:border-white/10 text-text-main hover:bg-black/5 dark:hover:bg-white/5",
|
||||
outline: "border border-black/15 dark:border-white/15 text-text-main hover:bg-black/5",
|
||||
ghost: "text-text-muted hover:bg-black/5 dark:hover:bg-white/5 hover:text-text-main",
|
||||
danger: "bg-red-500 text-white hover:bg-red-600 shadow-sm",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: "h-8 px-3 text-xs rounded-md",
|
||||
md: "h-10 px-5 text-sm rounded-lg",
|
||||
lg: "h-12 px-8 text-base rounded-xl",
|
||||
sm: "h-7 px-3 text-xs rounded-md",
|
||||
md: "h-9 px-4 text-sm rounded-lg",
|
||||
lg: "h-11 px-6 text-sm rounded-lg",
|
||||
};
|
||||
|
||||
export default function Button({
|
||||
|
|
@ -32,7 +32,7 @@ export default function Button({
|
|||
<button
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 cursor-pointer",
|
||||
"active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100",
|
||||
"active:scale-[0.99] disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100",
|
||||
variants[variant],
|
||||
sizes[size],
|
||||
fullWidth && "w-full",
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ export default function Card({
|
|||
<div
|
||||
className={cn(
|
||||
"bg-surface",
|
||||
"border border-border",
|
||||
"rounded-xl shadow-soft",
|
||||
hover && "hover:shadow-warm hover:border-primary/30 transition-all cursor-pointer",
|
||||
"border border-black/5 dark:border-white/5",
|
||||
"rounded-lg shadow-sm",
|
||||
hover && "hover:shadow-md hover:border-primary/30 transition-all cursor-pointer",
|
||||
paddings[padding],
|
||||
className
|
||||
)}
|
||||
|
|
@ -63,8 +63,8 @@ Card.Section = function CardSection({ children, className, ...props }) {
|
|||
<div
|
||||
className={cn(
|
||||
"p-4 rounded-lg",
|
||||
"bg-surface",
|
||||
"border border-border",
|
||||
"bg-black/[0.02] dark:bg-white/[0.02]",
|
||||
"border border-black/5 dark:border-white/5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -79,8 +79,9 @@ Card.Row = function CardRow({ children, className, ...props }) {
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"p-3 -mx-3 px-3 border-b border-border last:border-b-0 transition-colors",
|
||||
"hover:bg-sidebar",
|
||||
"p-3 -mx-3 px-3 transition-colors",
|
||||
"border-b border-black/5 dark:border-white/5 last:border-b-0",
|
||||
"hover:bg-black/[0.02] dark:hover:bg-white/[0.02]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -90,3 +91,31 @@ Card.Row = function CardRow({ children, className, ...props }) {
|
|||
);
|
||||
};
|
||||
|
||||
// Sub-component: List item with hover actions (macOS style)
|
||||
Card.ListItem = function CardListItem({
|
||||
children,
|
||||
actions,
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group flex items-center justify-between p-3 -mx-3 px-3",
|
||||
"border-b border-black/[0.03] dark:border-white/[0.03] last:border-b-0",
|
||||
"hover:bg-black/[0.02] dark:hover:bg-white/[0.02]",
|
||||
"transition-colors",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex-1 min-w-0">{children}</div>
|
||||
{actions && (
|
||||
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
{actions}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<header className="flex items-center justify-between px-8 py-5 border-b border-border bg-bg/80 backdrop-blur-md z-10 sticky top-0">
|
||||
<header className="flex items-center justify-between px-8 py-5 border-b border-black/5 dark:border-white/5 bg-bg/80 backdrop-blur-xl z-10 sticky top-0">
|
||||
{/* Mobile menu button */}
|
||||
<div className="flex items-center gap-3 lg:hidden">
|
||||
{showMenuButton && (
|
||||
|
|
|
|||
|
|
@ -38,17 +38,17 @@ export default function Input({
|
|||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"w-full py-2.5 px-4 text-sm text-text-main",
|
||||
"bg-surface border rounded-lg",
|
||||
"w-full py-2 px-3 text-sm text-text-main",
|
||||
"bg-white dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-md",
|
||||
"placeholder-text-muted/60",
|
||||
"focus:ring-2 focus:ring-primary/20 focus:border-primary focus:outline-none",
|
||||
"transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed",
|
||||
"focus:ring-1 focus:ring-primary/30 focus:border-primary/50 focus:outline-none",
|
||||
"transition-all shadow-inner disabled:opacity-50 disabled:cursor-not-allowed",
|
||||
// iOS zoom fix
|
||||
"text-[16px] sm:text-sm",
|
||||
icon && "pl-10",
|
||||
error
|
||||
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
|
||||
: "border-border",
|
||||
: "",
|
||||
inputClassName
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export default function Modal({
|
|||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
{/* Overlay */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||
className="absolute inset-0 bg-black/30 backdrop-blur-sm"
|
||||
onClick={closeOnOverlay ? onClose : undefined}
|
||||
/>
|
||||
|
||||
|
|
@ -60,8 +60,8 @@ export default function Modal({
|
|||
<div
|
||||
className={cn(
|
||||
"relative w-full bg-surface",
|
||||
"border border-border",
|
||||
"rounded-2xl shadow-elevated",
|
||||
"border border-black/10 dark:border-white/10",
|
||||
"rounded-xl shadow-2xl",
|
||||
"animate-in fade-in zoom-in-95 duration-200",
|
||||
sizes[size],
|
||||
className
|
||||
|
|
@ -69,16 +69,23 @@ export default function Modal({
|
|||
>
|
||||
{/* Header */}
|
||||
{(title || showCloseButton) && (
|
||||
<div className="flex items-center justify-between p-6 border-b border-border">
|
||||
{title && (
|
||||
<h2 className="text-lg font-semibold text-text-main">
|
||||
{title}
|
||||
</h2>
|
||||
)}
|
||||
<div className="flex items-center justify-between p-6 border-b border-black/5 dark:border-white/5">
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center gap-2 mr-4">
|
||||
<div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
|
||||
<div className="w-3 h-3 rounded-full bg-[#FFBD2E]" />
|
||||
<div className="w-3 h-3 rounded-full bg-[#27C93F]" />
|
||||
</div>
|
||||
{title && (
|
||||
<h2 className="text-lg font-semibold text-text-main">
|
||||
{title}
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
{showCloseButton && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1.5 rounded-lg text-text-muted hover:bg-black/5 transition-colors"
|
||||
className="p-1.5 rounded-lg text-text-muted hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[20px]">close</span>
|
||||
</button>
|
||||
|
|
@ -91,7 +98,7 @@ export default function Modal({
|
|||
|
||||
{/* Footer */}
|
||||
{footer && (
|
||||
<div className="flex items-center justify-end gap-3 p-6 border-t border-border">
|
||||
<div className="flex items-center justify-end gap-3 p-6 border-t border-black/5 dark:border-white/5">
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
48
src/shared/components/SegmentedControl.js
Normal file
48
src/shared/components/SegmentedControl.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@/shared/utils/cn";
|
||||
|
||||
export default function SegmentedControl({
|
||||
options = [],
|
||||
value,
|
||||
onChange,
|
||||
size = "md",
|
||||
className,
|
||||
}) {
|
||||
const sizes = {
|
||||
sm: "h-7 text-xs",
|
||||
md: "h-9 text-sm",
|
||||
lg: "h-11 text-base",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex items-center p-1 rounded-lg",
|
||||
"bg-black/5 dark:bg-white/5",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => onChange(option.value)}
|
||||
className={cn(
|
||||
"px-4 rounded-md font-medium transition-all",
|
||||
sizes[size],
|
||||
value === option.value
|
||||
? "bg-white dark:bg-white/10 text-text-main shadow-sm"
|
||||
: "text-text-muted hover:text-text-main"
|
||||
)}
|
||||
>
|
||||
{option.icon && (
|
||||
<span className="material-symbols-outlined text-[16px] mr-1.5">
|
||||
{option.icon}
|
||||
</span>
|
||||
)}
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -30,14 +30,14 @@ export default function Select({
|
|||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"w-full py-2.5 px-4 pr-10 text-sm text-text-main",
|
||||
"bg-surface border rounded-lg appearance-none",
|
||||
"focus:ring-2 focus:ring-primary/20 focus:border-primary focus:outline-none",
|
||||
"transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed",
|
||||
"w-full py-2 px-3 pr-10 text-sm text-text-main",
|
||||
"bg-white dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-md appearance-none",
|
||||
"focus:ring-1 focus:ring-primary/30 focus:border-primary/50 focus:outline-none",
|
||||
"transition-all disabled:opacity-50 disabled:cursor-not-allowed",
|
||||
"text-[16px] sm:text-sm",
|
||||
error
|
||||
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
|
||||
: "border-border",
|
||||
: "",
|
||||
selectClassName
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -62,9 +62,16 @@ export default function Sidebar({ onClose }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<aside className="flex w-72 flex-col border-r border-border bg-sidebar transition-colors duration-300">
|
||||
<aside className="flex w-72 flex-col border-r border-black/5 dark:border-white/5 bg-vibrancy backdrop-blur-xl transition-colors duration-300">
|
||||
{/* Traffic lights */}
|
||||
<div className="flex items-center gap-2 px-6 pt-5 pb-2">
|
||||
<div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
|
||||
<div className="w-3 h-3 rounded-full bg-[#FFBD2E]" />
|
||||
<div className="w-3 h-3 rounded-full bg-[#27C93F]" />
|
||||
</div>
|
||||
|
||||
{/* Logo */}
|
||||
<div className="p-8">
|
||||
<div className="px-6 py-4">
|
||||
<Link href="/dashboard" className="flex items-center gap-3">
|
||||
<div className="flex items-center justify-center size-9 rounded bg-linear-to-br from-[#f97815] to-[#c2590a]">
|
||||
<span className="material-symbols-outlined text-white text-[20px]">hub</span>
|
||||
|
|
@ -83,15 +90,15 @@ export default function Sidebar({ onClose }) {
|
|||
href={item.href}
|
||||
onClick={onClose}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-4 py-3 rounded-lg transition-all group",
|
||||
"flex items-center gap-3 px-4 py-2 rounded-lg transition-all group",
|
||||
isActive(item.href)
|
||||
? "bg-surface text-primary shadow-sm border border-border"
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-text-muted hover:bg-surface/50 hover:text-text-main"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"material-symbols-outlined text-[20px]",
|
||||
"material-symbols-outlined text-[18px]",
|
||||
isActive(item.href) ? "fill-1" : "group-hover:text-primary transition-colors"
|
||||
)}
|
||||
>
|
||||
|
|
@ -103,8 +110,8 @@ export default function Sidebar({ onClose }) {
|
|||
|
||||
{/* Debug section (only show when ENABLE_REQUEST_LOGS=true) */}
|
||||
{showDebug && (
|
||||
<div className="pt-6 mt-2">
|
||||
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-3">
|
||||
<div className="pt-4 mt-2">
|
||||
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-2">
|
||||
Debug
|
||||
</p>
|
||||
{debugItems.map((item) => (
|
||||
|
|
@ -113,15 +120,15 @@ export default function Sidebar({ onClose }) {
|
|||
href={item.href}
|
||||
onClick={onClose}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-4 py-3 rounded-lg transition-all group",
|
||||
"flex items-center gap-3 px-4 py-2 rounded-lg transition-all group",
|
||||
isActive(item.href)
|
||||
? "bg-surface text-primary shadow-sm border border-border"
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-text-muted hover:bg-surface/50 hover:text-text-main"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"material-symbols-outlined text-[20px]",
|
||||
"material-symbols-outlined text-[18px]",
|
||||
isActive(item.href) ? "fill-1" : "group-hover:text-primary transition-colors"
|
||||
)}
|
||||
>
|
||||
|
|
@ -134,8 +141,8 @@ export default function Sidebar({ onClose }) {
|
|||
)}
|
||||
|
||||
{/* System section */}
|
||||
<div className="pt-6 mt-2">
|
||||
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-3">
|
||||
<div className="pt-4 mt-2">
|
||||
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-2">
|
||||
System
|
||||
</p>
|
||||
{systemItems.map((item) => (
|
||||
|
|
@ -144,13 +151,18 @@ export default function Sidebar({ onClose }) {
|
|||
href={item.href}
|
||||
onClick={onClose}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-4 py-3 rounded-lg transition-all group",
|
||||
"flex items-center gap-3 px-4 py-2 rounded-lg transition-all group",
|
||||
isActive(item.href)
|
||||
? "bg-surface text-primary shadow-sm border border-border"
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-text-muted hover:bg-surface/50 hover:text-text-main"
|
||||
)}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[20px] group-hover:text-primary transition-colors">
|
||||
<span
|
||||
className={cn(
|
||||
"material-symbols-outlined text-[18px]",
|
||||
isActive(item.href) ? "fill-1" : "group-hover:text-primary transition-colors"
|
||||
)}
|
||||
>
|
||||
{item.icon}
|
||||
</span>
|
||||
<span className="text-sm font-medium">{item.label}</span>
|
||||
|
|
@ -160,11 +172,11 @@ export default function Sidebar({ onClose }) {
|
|||
</nav>
|
||||
|
||||
{/* Footer section */}
|
||||
<div className="p-4 border-t border-border">
|
||||
<div className="p-3 border-t border-black/5 dark:border-white/5">
|
||||
{/* Info message */}
|
||||
<div className="flex items-start gap-3 p-3 rounded-xl bg-surface border border-border mb-3">
|
||||
<div className="flex items-center justify-center size-8 rounded-lg bg-blue-500/10 text-blue-500 shrink-0 mt-0.5">
|
||||
<span className="material-symbols-outlined text-[18px]">info</span>
|
||||
<div className="flex items-start gap-2 p-2 rounded-lg bg-surface/50 mb-2">
|
||||
<div className="flex items-center justify-center size-6 rounded-md bg-blue-500/10 text-blue-500 shrink-0 mt-0.5">
|
||||
<span className="material-symbols-outlined text-[14px]">info</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-medium text-text-main leading-relaxed">
|
||||
|
|
|
|||
|
|
@ -52,11 +52,10 @@ export default function Toggle({
|
|||
className={cn(
|
||||
"relative inline-flex shrink-0 cursor-pointer rounded-full",
|
||||
"transition-colors duration-200 ease-in-out",
|
||||
"focus:outline-none focus:ring-2 focus:ring-primary/20 focus:ring-offset-2",
|
||||
"dark:focus:ring-offset-surface-dark",
|
||||
"focus:outline-none focus:ring-1 focus:ring-primary/30",
|
||||
checked
|
||||
? "bg-primary"
|
||||
: "bg-border",
|
||||
: "bg-black/10 dark:bg-white/20",
|
||||
sizes[size].track,
|
||||
disabled && "cursor-not-allowed"
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export { default as RequestLogger } from "./RequestLogger";
|
|||
export { default as KiroAuthModal } from "./KiroAuthModal";
|
||||
export { default as KiroOAuthWrapper } from "./KiroOAuthWrapper";
|
||||
export { default as KiroSocialOAuthModal } from "./KiroSocialOAuthModal";
|
||||
export { default as SegmentedControl } from "./SegmentedControl";
|
||||
|
||||
// Layouts
|
||||
export * from "./layouts";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default function DashboardLayout({ children }) {
|
|||
{/* Mobile sidebar overlay */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/50 lg:hidden"
|
||||
className="fixed inset-0 z-40 bg-black/20 lg:hidden"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue