Skip to content

Commit f514c1a

Browse files
committed
Update files
1 parent d4eed33 commit f514c1a

4 files changed

Lines changed: 332 additions & 124 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* PageHeader — consistent hero strip at the top of every authenticated
5+
* Account page.
6+
*
7+
* ┌─────────────────────────────────────────────────────────────────┐
8+
* │ (icon) Title [action slot] │
9+
* │ Description / supporting copy │
10+
* └─────────────────────────────────────────────────────────────────┘
11+
*
12+
* The whole block sits on a faint brand-tinted gradient + soft border so the
13+
* header reads as a single "frame" without dominating the content below.
14+
* `icon` is optional; when present it gets a glassy gradient tile.
15+
*/
16+
17+
import * as React from 'react';
18+
import { cn } from '@/lib/utils';
19+
20+
export interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
21+
title: React.ReactNode;
22+
description?: React.ReactNode;
23+
icon?: React.ComponentType<{ className?: string }>;
24+
actions?: React.ReactNode;
25+
}
26+
27+
export function PageHeader({
28+
title,
29+
description,
30+
icon: Icon,
31+
actions,
32+
className,
33+
...rest
34+
}: PageHeaderProps) {
35+
return (
36+
<div
37+
className={cn(
38+
'relative overflow-hidden rounded-xl border bg-brand-gradient-subtle',
39+
'px-5 py-5 sm:px-6 sm:py-6',
40+
className,
41+
)}
42+
{...rest}
43+
>
44+
<div
45+
className="pointer-events-none absolute inset-0 opacity-60"
46+
style={{
47+
backgroundImage:
48+
'radial-gradient(ellipse 60% 80% at 0% 0%, hsl(var(--brand-from) / 0.12), transparent 60%)',
49+
}}
50+
aria-hidden
51+
/>
52+
<div className="relative flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
53+
<div className="flex min-w-0 items-start gap-4">
54+
{Icon ? (
55+
<div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-brand-gradient text-primary-foreground shadow-sm shadow-primary/30 ring-1 ring-white/15">
56+
<Icon className="size-5" />
57+
</div>
58+
) : null}
59+
<div className="min-w-0 space-y-1">
60+
<h1 className="truncate text-xl font-semibold tracking-tight sm:text-2xl">
61+
{title}
62+
</h1>
63+
{description ? (
64+
<p className="text-sm text-muted-foreground">{description}</p>
65+
) : null}
66+
</div>
67+
</div>
68+
{actions ? (
69+
<div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div>
70+
) : null}
71+
</div>
72+
</div>
73+
);
74+
}

apps/account/src/index.css

Lines changed: 121 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -61,82 +61,162 @@
6161
}
6262
}
6363

64+
/* ─── Brand palette ──────────────────────────────────────────────────────────
65+
* ObjectStack Account uses a violet → indigo brand gradient.
66+
* - `--brand-from` : violet (primary brand hue)
67+
* - `--brand-to` : indigo (gradient terminus)
68+
* `--primary` is wired to `--brand-from` so every default button, focus ring,
69+
* link colour and active-state automatically picks up the brand identity.
70+
* The composite gradients (`--brand-gradient`, `--brand-gradient-subtle`) are
71+
* intended for hero surfaces, the login split-screen panel and the page-header
72+
* banner. Tweak the two HSL triplets to re-skin the whole portal.
73+
*/
6474
:root {
65-
--radius: 0.625rem;
75+
--radius: 0.75rem;
76+
77+
--brand-from: 262 83% 58%;
78+
--brand-to: 221 83% 53%;
79+
--brand-gradient: linear-gradient(135deg, hsl(var(--brand-from)), hsl(var(--brand-to)));
80+
--brand-gradient-subtle: linear-gradient(
81+
135deg,
82+
hsl(var(--brand-from) / 0.08),
83+
hsl(var(--brand-to) / 0.08)
84+
);
85+
6686
--background: 0 0% 100%;
6787
--foreground: 240 10% 3.9%;
6888
--card: 0 0% 100%;
6989
--card-foreground: 240 10% 3.9%;
7090
--popover: 0 0% 100%;
7191
--popover-foreground: 240 10% 3.9%;
72-
--primary: 240 5.9% 10%;
92+
--primary: var(--brand-from);
7393
--primary-foreground: 0 0% 98%;
7494
--secondary: 240 4.8% 95.9%;
7595
--secondary-foreground: 240 5.9% 10%;
7696
--muted: 240 4.8% 95.9%;
7797
--muted-foreground: 240 3.8% 46.1%;
78-
--accent: 240 4.8% 95.9%;
79-
--accent-foreground: 240 5.9% 10%;
98+
--accent: 262 83% 96%;
99+
--accent-foreground: 262 60% 30%;
80100
--destructive: 0 84.2% 60.2%;
81101
--destructive-foreground: 0 0% 98%;
82102
--border: 240 5.9% 90%;
83103
--input: 240 5.9% 90%;
84-
--ring: 240 5.9% 10%;
85-
--chart-1: 12 76% 61%;
86-
--chart-2: 173 58% 39%;
87-
--chart-3: 197 37% 24%;
104+
--ring: var(--brand-from);
105+
--chart-1: 262 83% 58%;
106+
--chart-2: 221 83% 53%;
107+
--chart-3: 173 58% 39%;
88108
--chart-4: 43 74% 66%;
89109
--chart-5: 27 87% 67%;
90-
--sidebar-background: 0 0% 98%;
110+
--sidebar-background: 0 0% 98.5%;
91111
--sidebar-foreground: 240 5.3% 26.1%;
92-
--sidebar-primary: 240 5.9% 10%;
112+
--sidebar-primary: var(--brand-from);
93113
--sidebar-primary-foreground: 0 0% 98%;
94-
--sidebar-accent: 240 4.8% 95.9%;
95-
--sidebar-accent-foreground: 240 5.9% 10%;
114+
--sidebar-accent: 262 83% 96%;
115+
--sidebar-accent-foreground: 262 60% 30%;
96116
--sidebar-border: 220 13% 91%;
97-
--sidebar-ring: 240 5.9% 10%;
117+
--sidebar-ring: var(--brand-from);
98118
}
99119

100120
.dark {
101-
--background: 240 10% 3.9%;
121+
--background: 222 47% 6%;
102122
--foreground: 0 0% 98%;
103-
--card: 240 10% 3.9%;
123+
--card: 222 47% 8%;
104124
--card-foreground: 0 0% 98%;
105-
--popover: 240 10% 3.9%;
125+
--popover: 222 47% 8%;
106126
--popover-foreground: 0 0% 98%;
107-
--primary: 0 0% 98%;
108-
--primary-foreground: 240 5.9% 10%;
109-
--secondary: 240 3.7% 15.9%;
127+
--primary: 262 83% 68%;
128+
--primary-foreground: 222 47% 6%;
129+
--secondary: 222 32% 14%;
110130
--secondary-foreground: 0 0% 98%;
111-
--muted: 240 3.7% 15.9%;
112-
--muted-foreground: 240 5% 64.9%;
113-
--accent: 240 3.7% 15.9%;
114-
--accent-foreground: 0 0% 98%;
115-
--destructive: 0 62.8% 30.6%;
131+
--muted: 222 32% 14%;
132+
--muted-foreground: 220 9% 64%;
133+
--accent: 262 50% 20%;
134+
--accent-foreground: 262 80% 90%;
135+
--destructive: 0 62.8% 50%;
116136
--destructive-foreground: 0 0% 98%;
117-
--border: 240 3.7% 15.9%;
118-
--input: 240 3.7% 15.9%;
119-
--ring: 240 4.9% 83.9%;
120-
--chart-1: 220 70% 50%;
121-
--chart-2: 160 60% 45%;
122-
--chart-3: 30 80% 55%;
123-
--chart-4: 280 65% 60%;
124-
--chart-5: 340 75% 55%;
125-
--sidebar-background: 240 5.9% 10%;
126-
--sidebar-foreground: 240 4.8% 95.9%;
127-
--sidebar-primary: 224.3 76.3% 48%;
128-
--sidebar-primary-foreground: 0 0% 100%;
129-
--sidebar-accent: 240 3.7% 15.9%;
130-
--sidebar-accent-foreground: 240 4.8% 95.9%;
131-
--sidebar-border: 240 3.7% 15.9%;
132-
--sidebar-ring: 240 4.9% 83.9%;
137+
--border: 222 32% 16%;
138+
--input: 222 32% 16%;
139+
--ring: 262 83% 68%;
140+
--chart-1: 262 83% 68%;
141+
--chart-2: 221 83% 63%;
142+
--chart-3: 173 58% 49%;
143+
--chart-4: 43 74% 66%;
144+
--chart-5: 27 87% 67%;
145+
--sidebar-background: 222 47% 7%;
146+
--sidebar-foreground: 220 9% 80%;
147+
--sidebar-primary: 262 83% 68%;
148+
--sidebar-primary-foreground: 222 47% 6%;
149+
--sidebar-accent: 262 50% 18%;
150+
--sidebar-accent-foreground: 262 80% 90%;
151+
--sidebar-border: 222 32% 14%;
152+
--sidebar-ring: 262 83% 68%;
133153
}
134154

135155
@layer base {
136156
* {
137157
@apply border-border outline-ring/50;
138158
}
139159
body {
140-
@apply bg-background text-foreground;
160+
@apply bg-background text-foreground antialiased;
161+
font-feature-settings: "rlig" 1, "calt" 1, "ss01" 1;
162+
}
163+
}
164+
165+
@layer utilities {
166+
/* ─── Brand surface helpers ───────────────────────────────────────────────
167+
* Apply on any element that should sit on the brand gradient (login hero
168+
* panel, top-bar logo tile, primary CTA, etc.). The `-subtle` variant is a
169+
* 8%-opacity wash for page headers and empty-state cards.
170+
*/
171+
.bg-brand-gradient {
172+
background-image: var(--brand-gradient);
173+
}
174+
.bg-brand-gradient-subtle {
175+
background-image: var(--brand-gradient-subtle);
176+
}
177+
.text-brand-gradient {
178+
background-image: var(--brand-gradient);
179+
-webkit-background-clip: text;
180+
background-clip: text;
181+
color: transparent;
182+
}
183+
.ring-brand {
184+
--tw-ring-color: hsl(var(--brand-from) / 0.35);
141185
}
186+
187+
/* Soft radial "spotlight" used on hero / empty-state surfaces. */
188+
.bg-brand-spotlight {
189+
background-image:
190+
radial-gradient(ellipse 80% 50% at 50% -20%, hsl(var(--brand-from) / 0.18), transparent 60%),
191+
radial-gradient(ellipse 60% 50% at 100% 100%, hsl(var(--brand-to) / 0.18), transparent 60%);
192+
}
193+
194+
/* Mesh/grid backdrop for the login split-screen brand column. */
195+
.bg-brand-mesh {
196+
background-color: hsl(var(--brand-from));
197+
background-image:
198+
radial-gradient(at 20% 20%, hsl(var(--brand-from)) 0%, transparent 50%),
199+
radial-gradient(at 80% 0%, hsl(var(--brand-to)) 0%, transparent 50%),
200+
radial-gradient(at 0% 80%, hsl(280 90% 60%) 0%, transparent 50%),
201+
radial-gradient(at 80% 80%, hsl(199 89% 48%) 0%, transparent 50%);
202+
}
203+
}
204+
205+
/* ─── Page transition ────────────────────────────────────────────────────────
206+
* Tiny fade-up applied to route content; opt in by adding `.page-enter`
207+
* to a wrapper around <Outlet/>.
208+
*/
209+
@keyframes page-enter {
210+
from {
211+
opacity: 0;
212+
transform: translateY(4px);
213+
}
214+
to {
215+
opacity: 1;
216+
transform: translateY(0);
217+
}
218+
}
219+
220+
.page-enter {
221+
animation: page-enter 240ms cubic-bezier(0.16, 1, 0.3, 1);
142222
}

apps/account/src/routes/account.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import { createFileRoute, Outlet } from '@tanstack/react-router';
1313
import { useObjectTranslation } from '@object-ui/i18n';
14+
import { User } from 'lucide-react';
15+
import { PageHeader } from '@/components/page-header';
1416
import { useSession } from '@/hooks/useSession';
1517

1618
export const Route = createFileRoute('/account')({
@@ -23,13 +25,13 @@ function AccountLayout() {
2325

2426
return (
2527
<div className="flex flex-1 flex-col overflow-hidden">
26-
<div className="flex-1 overflow-auto px-6 py-8">
27-
<div className="mx-auto max-w-3xl space-y-6">
28-
<div>
29-
<h1 className="text-2xl font-semibold">{t('topBar.breadcrumb.account')}</h1>
30-
<p className="text-sm text-muted-foreground">{user?.email}</p>
31-
</div>
32-
28+
<div className="flex-1 overflow-auto px-4 py-6 sm:px-6 sm:py-8">
29+
<div className="page-enter mx-auto flex max-w-4xl flex-col gap-6">
30+
<PageHeader
31+
icon={User}
32+
title={t('topBar.breadcrumb.account')}
33+
description={user?.email ?? undefined}
34+
/>
3335
<Outlet />
3436
</div>
3537
</div>

0 commit comments

Comments
 (0)