@nocobase/app-template-default 1.0.0-beta.67 → 1.0.0-beta.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/rules/components.md +1 -1
- package/.agents/rules/shell.md +3 -1
- package/client/components/page-header.tsx +1 -1
- package/client/components/status-badge.tsx +7 -1
- package/client/extensions/nocobase-inbox/inbox-item.tsx +44 -58
- package/client/extensions/nocobase-inbox/inbox-list.tsx +73 -75
- package/client/index.tsx +4 -0
- package/client/layouts/components/app-sidebar.tsx +5 -5
- package/client/layouts/components/layout-header.tsx +1 -1
- package/client/layouts/components/navigation-menu.tsx +223 -12
- package/client/layouts/components/sidebar-footer.tsx +1 -1
- package/client/layouts/components/user-menu.tsx +1 -1
- package/client/lib/navigation-transform.ts +8 -0
- package/client/lib/scrollbar.ts +88 -0
- package/client/locales/system/en-US.ts +4 -0
- package/client/locales/system/zh-CN.ts +15 -12
- package/client/pages/auth/auth-page.css +37 -0
- package/client/pages/auth/reset-password.tsx +57 -14
- package/client/pages/auth/shared.tsx +24 -24
- package/client/pages/home/index.tsx +2 -2
- package/client/routing/route-navigation.ts +10 -1
- package/client/styles.css +253 -17
- package/client/theme/theme-settings.tsx +12 -7
- package/client/theme/themes/compact.css +9 -14
- package/client/theme/themes/default.css +7 -12
- package/package.json +7 -7
- package/server/app.ts +2 -0
- package/server/config/notification.ts +4 -7
- package/tests/components/auth-i18n.test.tsx +57 -5
- package/tests/components/layout-i18n.test.tsx +2 -2
- package/tests/components/navigation-menu.test.tsx +133 -4
- package/tests/components/sidebar-permissions.test.tsx +5 -15
- package/tests/components/status-badge.test.tsx +22 -0
- package/tests/logic/scrollbar.test.ts +88 -0
- package/tests/logic/theme-tokens.test.ts +1 -0
- package/vitest.config.ts +1 -0
|
@@ -5,6 +5,8 @@ import type { ReactElement, ReactNode } from 'react';
|
|
|
5
5
|
|
|
6
6
|
import { AuthSplitLayout } from '#extensions/nocobase-auth-split-layout/auth-split-layout';
|
|
7
7
|
|
|
8
|
+
import './auth-page.css';
|
|
9
|
+
|
|
8
10
|
export interface AuthPageProps {
|
|
9
11
|
readonly title: ReactNode;
|
|
10
12
|
readonly description: ReactNode;
|
|
@@ -26,6 +28,7 @@ export function AuthPage({
|
|
|
26
28
|
<AuthSplitLayout
|
|
27
29
|
aside={<BrandPanel />}
|
|
28
30
|
asideLabel={t('auth.about')}
|
|
31
|
+
className='auth-page'
|
|
29
32
|
description={description}
|
|
30
33
|
logo={
|
|
31
34
|
<>
|
|
@@ -68,42 +71,39 @@ function BrandPanel(): ReactElement {
|
|
|
68
71
|
];
|
|
69
72
|
|
|
70
73
|
return (
|
|
71
|
-
<div className='relative grid h-full place-items-center overflow-hidden
|
|
72
|
-
<div className='
|
|
73
|
-
<div className='relative w-full max-w-xl'>
|
|
74
|
+
<div className='auth-brand-panel relative grid h-full place-items-center overflow-hidden p-12 text-white'>
|
|
75
|
+
<div className='relative w-full max-w-md'>
|
|
74
76
|
<p className='text-xs font-semibold tracking-[0.16em] text-(--auth-brand-highlight) uppercase'>
|
|
75
77
|
{t('auth.platform')}
|
|
76
78
|
</p>
|
|
77
|
-
<h2 className='mt-
|
|
79
|
+
<h2 className='mt-6 text-4xl leading-tight font-semibold tracking-tight'>
|
|
78
80
|
{t('auth.marketingTitleFirst')}
|
|
79
81
|
<br />
|
|
80
82
|
{t('auth.marketingTitleSecond')}
|
|
81
83
|
<br />
|
|
82
84
|
{t('auth.marketingTitleThird')}
|
|
83
85
|
</h2>
|
|
84
|
-
<p className='mt-
|
|
86
|
+
<p className='mt-6 text-sm leading-7 text-(--auth-brand-muted)'>
|
|
85
87
|
{t('auth.marketingDescription')}
|
|
86
88
|
</p>
|
|
87
|
-
<div className='mt-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
</p>
|
|
99
|
-
</div>
|
|
89
|
+
<div className='mt-12 space-y-8'>
|
|
90
|
+
{features.map(({ description, icon: Icon, id, title }) => (
|
|
91
|
+
<div className='flex gap-4' key={id}>
|
|
92
|
+
<span className='grid size-10 shrink-0 place-items-center rounded-xl bg-(--auth-brand-highlight)/8 text-(--auth-brand-highlight)'>
|
|
93
|
+
<Icon aria-hidden='true' className='size-5' />
|
|
94
|
+
</span>
|
|
95
|
+
<div>
|
|
96
|
+
<p className='text-sm font-medium'>{title}</p>
|
|
97
|
+
<p className='mt-1.5 text-sm leading-6 text-(--auth-brand-muted)'>
|
|
98
|
+
{description}
|
|
99
|
+
</p>
|
|
100
100
|
</div>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
</
|
|
101
|
+
</div>
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
<div className='mt-12 flex items-center gap-2.5 border-t border-white/8 pt-6 text-xs text-(--auth-brand-muted)'>
|
|
105
|
+
<Blocks aria-hidden='true' className='size-4' />
|
|
106
|
+
<span>{t('auth.marketingFooter')}</span>
|
|
107
107
|
</div>
|
|
108
108
|
</div>
|
|
109
109
|
</div>
|
|
@@ -160,11 +160,11 @@ function CapabilityCard({
|
|
|
160
160
|
onClick={onOpen}
|
|
161
161
|
className='group flex w-full cursor-pointer rounded-xl text-left outline-none focus-visible:ring-3 focus-visible:ring-ring/50'
|
|
162
162
|
>
|
|
163
|
-
<Card className='w-full transition-[translate,box-shadow] duration-200 ease-out group-hover:-translate-y-0.5 group-hover:
|
|
163
|
+
<Card className='w-full transition-[translate,box-shadow,background-color] duration-200 ease-out group-hover:-translate-y-0.5 group-hover:bg-hover group-hover:shadow-md motion-reduce:transition-none motion-reduce:group-hover:translate-y-0'>
|
|
164
164
|
<CardHeader className='gap-3'>
|
|
165
165
|
{/* The chip only deepens its tint on hover rather than inverting: swapping a light background and a dark icon
|
|
166
166
|
passes both through the same color halfway, where the icon vanishes for a moment and reads as a flicker. */}
|
|
167
|
-
<span className='flex size-9 items-center justify-center rounded-lg bg-primary/5 text-primary ring-1 ring-transparent transition-[background-color,box-shadow] duration-200 ease-out group-hover:bg-primary/10 group-hover:ring-
|
|
167
|
+
<span className='flex size-9 items-center justify-center rounded-lg bg-primary/5 text-primary ring-1 ring-transparent transition-[background-color,box-shadow] duration-200 ease-out group-hover:bg-primary/10 group-hover:ring-border motion-reduce:transition-none'>
|
|
168
168
|
<Icon
|
|
169
169
|
className='size-4 transition-transform duration-200 ease-out group-hover:scale-110 motion-reduce:transition-none'
|
|
170
170
|
aria-hidden
|
|
@@ -8,8 +8,10 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
8
8
|
import { matchPath, matchRoutes, type RouteObject } from 'react-router';
|
|
9
9
|
|
|
10
10
|
import { EMPTY_ARRAY } from '#lib/constants';
|
|
11
|
+
import { transformNavigation } from '#lib/navigation-transform';
|
|
11
12
|
|
|
12
13
|
export interface RouteNavigationItem {
|
|
14
|
+
readonly section?: boolean;
|
|
13
15
|
readonly route: AppClientRegisteredRoute;
|
|
14
16
|
readonly children: readonly RouteNavigationItem[];
|
|
15
17
|
}
|
|
@@ -17,10 +19,17 @@ export interface RouteNavigationItem {
|
|
|
17
19
|
export function buildRouteNavigation(
|
|
18
20
|
routes: readonly AppClientRegisteredRoute[],
|
|
19
21
|
denied: ReadonlySet<string>,
|
|
22
|
+
): RouteNavigationItem[] {
|
|
23
|
+
return transformNavigation(visibleRouteNavigation(routes, denied));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function visibleRouteNavigation(
|
|
27
|
+
routes: readonly AppClientRegisteredRoute[],
|
|
28
|
+
denied: ReadonlySet<string>,
|
|
20
29
|
): RouteNavigationItem[] {
|
|
21
30
|
return routes.flatMap((route) => {
|
|
22
31
|
if (denied.has(routeKey(route))) return [];
|
|
23
|
-
const children =
|
|
32
|
+
const children = visibleRouteNavigation(
|
|
24
33
|
route.children ?? EMPTY_ARRAY,
|
|
25
34
|
denied,
|
|
26
35
|
);
|
package/client/styles.css
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
--color-muted-foreground: var(--muted-foreground);
|
|
51
51
|
--color-accent: var(--accent);
|
|
52
52
|
--color-accent-foreground: var(--accent-foreground);
|
|
53
|
+
--color-hover: var(--hover);
|
|
53
54
|
--color-destructive: var(--destructive);
|
|
54
55
|
--color-border: var(--border);
|
|
55
56
|
--color-input: var(--input);
|
|
@@ -138,16 +139,28 @@
|
|
|
138
139
|
|
|
139
140
|
/* Application-wide interaction and badge treatment, outside generated primitives. */
|
|
140
141
|
@layer base {
|
|
142
|
+
/* Whatever may be clicked shows the pointer, the plugins' pages included.
|
|
143
|
+
Tailwind 4 leaves buttons with the arrow; a control disabled (natively,
|
|
144
|
+
or as Base UI and ARIA mark it) keeps it, and a utility on an element
|
|
145
|
+
(`cursor-default`, `cursor-not-allowed`) still wins over this layer. */
|
|
141
146
|
:is(
|
|
142
147
|
button,
|
|
143
148
|
[role='button'],
|
|
144
149
|
[role='menuitem'],
|
|
150
|
+
[role='menuitemcheckbox'],
|
|
151
|
+
[role='menuitemradio'],
|
|
145
152
|
[role='option'],
|
|
146
153
|
[role='tab'],
|
|
147
154
|
[role='checkbox'],
|
|
148
155
|
[role='radio'],
|
|
149
156
|
[role='switch'],
|
|
150
157
|
[role='combobox'],
|
|
158
|
+
summary,
|
|
159
|
+
select,
|
|
160
|
+
label[for],
|
|
161
|
+
input[type='checkbox'],
|
|
162
|
+
input[type='radio'],
|
|
163
|
+
input[type='file'],
|
|
151
164
|
a[href]
|
|
152
165
|
):not(:disabled):not([aria-disabled='true']):not([data-disabled]) {
|
|
153
166
|
cursor: pointer;
|
|
@@ -161,14 +174,64 @@
|
|
|
161
174
|
padding-inline: calc(var(--spacing) * 2.5);
|
|
162
175
|
line-height: 1.4;
|
|
163
176
|
}
|
|
177
|
+
/* The small badge, `data-size='sm'`: beside a title or a name, on a card or
|
|
178
|
+
a board's card, wherever several sit in a dense row. The default one is a
|
|
179
|
+
status standing on its own, as in a table's column or a record's
|
|
180
|
+
particulars. */
|
|
181
|
+
[data-slot='badge'][data-size='sm'] {
|
|
182
|
+
min-height: calc(var(--spacing) * 5.5);
|
|
183
|
+
padding-block: 0;
|
|
184
|
+
padding-inline: calc(var(--spacing) * 1.5);
|
|
185
|
+
font-size: var(--text-xs);
|
|
186
|
+
line-height: 1.3;
|
|
187
|
+
}
|
|
164
188
|
|
|
165
|
-
/*
|
|
189
|
+
/* One focus for every control, across the application and the plugins'
|
|
190
|
+
pages: a half-strength border and a 2px halo at a fifth of the ring colour,
|
|
191
|
+
in place of the 3px ring at half of it that the primitives and pages draw,
|
|
192
|
+
which reads heavy. Unlayered, so it wins over their utilities without
|
|
193
|
+
editing them. A label wrapping a visually hidden control, such as an
|
|
194
|
+
option card, and a rich text editor's frame show their control's focus.
|
|
195
|
+
An invalid field keeps its red, and a destructive button its own colour. */
|
|
196
|
+
:is(
|
|
197
|
+
[data-slot='input'],
|
|
198
|
+
[data-slot='textarea'],
|
|
199
|
+
[data-slot='select-trigger'],
|
|
200
|
+
button,
|
|
201
|
+
[role='button'],
|
|
202
|
+
[role='tab'],
|
|
203
|
+
[role='menuitem'],
|
|
204
|
+
[role='checkbox'],
|
|
205
|
+
[role='switch'],
|
|
206
|
+
[role='radio'],
|
|
207
|
+
input[type='checkbox'],
|
|
208
|
+
input[type='radio'],
|
|
209
|
+
a[href]
|
|
210
|
+
):focus-visible:not([aria-invalid='true']),
|
|
211
|
+
label:has(> .sr-only:focus-visible),
|
|
212
|
+
[data-slot='rich-text-editor']:focus-within,
|
|
213
|
+
[data-slot='input-group']:has(
|
|
214
|
+
[data-slot='input-group-control']:focus-visible
|
|
215
|
+
):not(:has([aria-invalid='true'])) {
|
|
216
|
+
border-color: color-mix(in oklch, var(--ring) 50%, transparent);
|
|
217
|
+
box-shadow: 0 0 0 2px color-mix(in oklch, var(--ring) 20%, transparent);
|
|
218
|
+
}
|
|
219
|
+
[data-slot='button'].text-destructive:focus-visible:not([aria-invalid='true']) {
|
|
220
|
+
border-color: color-mix(in oklch, var(--destructive) 40%, transparent);
|
|
221
|
+
box-shadow: 0 0 0 2px color-mix(in oklch, var(--destructive) 20%, transparent);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* Overlays in a light frame: a faint, blurred backdrop, and the content
|
|
225
|
+
framed by the general border alone, as a card is, without the primitive's
|
|
226
|
+
ring or a heavy shadow. The plugins' copies of these primitives take it
|
|
227
|
+
too; unlayered, so it wins over their utilities. */
|
|
166
228
|
:is(
|
|
167
229
|
[data-slot='dialog-overlay'],
|
|
168
230
|
[data-slot='alert-dialog-overlay'],
|
|
169
231
|
[data-slot='sheet-overlay']
|
|
170
232
|
) {
|
|
171
|
-
background:
|
|
233
|
+
background: rgb(0 0 0 / 0.1);
|
|
234
|
+
backdrop-filter: blur(4px);
|
|
172
235
|
}
|
|
173
236
|
:is(
|
|
174
237
|
[data-slot='dialog-content'],
|
|
@@ -176,7 +239,7 @@
|
|
|
176
239
|
[data-slot='route-overlay-content']
|
|
177
240
|
) {
|
|
178
241
|
border: 1px solid var(--border);
|
|
179
|
-
box-shadow:
|
|
242
|
+
box-shadow: none;
|
|
180
243
|
}
|
|
181
244
|
[data-slot='route-overlay-content'][data-presentation='dialog'] {
|
|
182
245
|
border-radius: var(--radius-xl);
|
|
@@ -308,14 +371,65 @@
|
|
|
308
371
|
padding-right: calc(var(--spacing) * 24);
|
|
309
372
|
}
|
|
310
373
|
|
|
311
|
-
/*
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
374
|
+
/* The navigation's rows are roomier than the sidebar primitive's: 10 units
|
|
375
|
+
high with half a unit between them, nested rows 8.5 high, in a sidebar 64
|
|
376
|
+
units wide (layouts/components/app-sidebar.tsx). The primitive's important
|
|
377
|
+
icon-mode sizes still control the collapsed rail. */
|
|
378
|
+
.app-navigation [data-slot='sidebar-menu'] {
|
|
379
|
+
gap: calc(var(--spacing) * 0.5);
|
|
380
|
+
}
|
|
381
|
+
/* Top-level groups only: a section nested in a menu sits flush. */
|
|
382
|
+
.app-navigation > nav > [data-slot='sidebar-group'] {
|
|
383
|
+
padding: calc(var(--spacing) * 2);
|
|
384
|
+
}
|
|
385
|
+
/* A section's heading reads as a heading over its entries, not as one of
|
|
386
|
+
them: half the menu's foreground, mixed toward its ground, so both colour
|
|
387
|
+
schemes follow. */
|
|
388
|
+
.app-navigation [data-sidebar='group-label'] {
|
|
389
|
+
height: calc(var(--spacing) * 8);
|
|
390
|
+
padding-inline: calc(var(--spacing) * 3.5);
|
|
391
|
+
color: color-mix(in oklch, var(--sidebar-foreground) 50%, var(--sidebar));
|
|
392
|
+
}
|
|
393
|
+
.app-navigation [data-slot='sidebar-menu-button'] {
|
|
394
|
+
height: calc(var(--spacing) * 10);
|
|
395
|
+
gap: calc(var(--spacing) * 2);
|
|
396
|
+
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3.5);
|
|
397
|
+
}
|
|
398
|
+
/* The primitive places a row's count and its expand button for 8-unit rows;
|
|
399
|
+
both are 5 units high, so this centres them on the taller rows. */
|
|
400
|
+
.app-navigation
|
|
401
|
+
[data-slot='sidebar-menu-item']
|
|
402
|
+
> :is([data-slot='sidebar-menu-badge'], [data-slot='sidebar-menu-action']) {
|
|
403
|
+
top: calc(var(--spacing) * 2.5);
|
|
404
|
+
}
|
|
405
|
+
.app-navigation
|
|
406
|
+
[data-slot='sidebar-menu-sub-item']
|
|
407
|
+
> [data-slot='sidebar-menu-action'] {
|
|
408
|
+
top: calc(var(--spacing) * 1.75);
|
|
409
|
+
}
|
|
410
|
+
.app-navigation [data-slot='sidebar-menu-button'] > svg {
|
|
411
|
+
width: calc(var(--spacing) * 4);
|
|
412
|
+
height: calc(var(--spacing) * 4);
|
|
413
|
+
}
|
|
414
|
+
/* An entry with a count (navigation-menu.tsx) leaves room for it at the
|
|
415
|
+
row's end, so a long label truncates before reaching it. */
|
|
416
|
+
.app-navigation
|
|
417
|
+
[data-slot='sidebar-menu-item']:has(> [data-slot='sidebar-menu-badge'])
|
|
418
|
+
> [data-slot='sidebar-menu-button'] {
|
|
419
|
+
padding-right: calc(var(--spacing) * 10);
|
|
420
|
+
}
|
|
421
|
+
.app-navigation [data-slot='sidebar-menu-sub'] {
|
|
422
|
+
gap: calc(var(--spacing) * 0.5);
|
|
423
|
+
padding-block: calc(var(--spacing) * 1);
|
|
424
|
+
}
|
|
425
|
+
.app-navigation [data-slot='sidebar-menu-sub-button'] {
|
|
426
|
+
height: calc(var(--spacing) * 8.5);
|
|
427
|
+
gap: calc(var(--spacing) * 2);
|
|
428
|
+
padding-inline: calc(var(--spacing) * 2.5);
|
|
316
429
|
}
|
|
317
|
-
|
|
318
|
-
|
|
430
|
+
.app-navigation [data-slot='sidebar-menu-sub-button'] > svg {
|
|
431
|
+
width: calc(var(--spacing) * 4);
|
|
432
|
+
height: calc(var(--spacing) * 4);
|
|
319
433
|
}
|
|
320
434
|
|
|
321
435
|
/* Brand tint belongs to the application shell, independent of each page's content. */
|
|
@@ -331,11 +445,20 @@
|
|
|
331
445
|
|
|
332
446
|
/* Shared table hierarchy stays neutral so semantic status colors remain distinct. */
|
|
333
447
|
[data-slot='table-header'] {
|
|
334
|
-
background:
|
|
448
|
+
background: var(--hover);
|
|
335
449
|
}
|
|
336
450
|
[data-slot='table-header'] [data-slot='table-row']:hover {
|
|
337
451
|
background: transparent;
|
|
338
452
|
}
|
|
453
|
+
/* A row under the pointer takes the --hover grey, as every hovered row and
|
|
454
|
+
list item does, rather than the primitive's half of --muted. Matched by
|
|
455
|
+
that class, so a row given another hover keeps it, and a selected row
|
|
456
|
+
keeps its own ground. */
|
|
457
|
+
[data-slot='table-row'][class~='hover:bg-muted/50']:hover:not(
|
|
458
|
+
[data-state='selected']
|
|
459
|
+
) {
|
|
460
|
+
background: var(--hover);
|
|
461
|
+
}
|
|
339
462
|
[data-slot='table-head'] {
|
|
340
463
|
height: calc(var(--spacing) * 12);
|
|
341
464
|
padding-inline: calc(var(--spacing) * 3);
|
|
@@ -355,12 +478,125 @@
|
|
|
355
478
|
color: var(--primary);
|
|
356
479
|
}
|
|
357
480
|
|
|
358
|
-
/*
|
|
359
|
-
|
|
481
|
+
/* A card's frame in the general border, as every other line: the card
|
|
482
|
+
primitive and the plugins' copies draw their ring at a tenth of the
|
|
483
|
+
foreground. Matched by that class, so a card given another ring colour
|
|
484
|
+
keeps it; unlayered, so it wins over the utility. */
|
|
485
|
+
[data-slot='card'][class~='ring-foreground/10'] {
|
|
486
|
+
--tw-ring-color: var(--border);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* Line tabs as a record page's tabs: from the left, the open one in the
|
|
490
|
+
foreground over a 2px line that sits on a thin rule under the row, the
|
|
491
|
+
others muted until hovered. Unlayered, so it wins over the primitive's
|
|
492
|
+
utilities. A row whose tabs keep off its edges sets --tabs-inset; its rule
|
|
493
|
+
still runs edge to edge. */
|
|
494
|
+
[data-slot='tabs-list'][data-variant='line'][data-orientation='horizontal'] {
|
|
495
|
+
width: 100%;
|
|
496
|
+
height: auto;
|
|
497
|
+
justify-content: flex-start;
|
|
498
|
+
gap: calc(var(--spacing) * 5);
|
|
499
|
+
padding: 0 var(--tabs-inset, 0px);
|
|
500
|
+
border-bottom: 1px solid var(--border);
|
|
501
|
+
}
|
|
502
|
+
[data-slot='tabs-list'][data-variant='line'][data-orientation='horizontal']
|
|
503
|
+
> [data-slot='tabs-trigger'] {
|
|
504
|
+
flex: none;
|
|
360
505
|
height: calc(var(--spacing) * 10);
|
|
361
|
-
padding-inline:
|
|
506
|
+
padding-inline: 0;
|
|
507
|
+
border-inline-width: 0;
|
|
508
|
+
color: var(--muted-foreground);
|
|
362
509
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
510
|
+
[data-slot='tabs-list'][data-variant='line'][data-orientation='horizontal']
|
|
511
|
+
> [data-slot='tabs-trigger']:is(:hover, [data-active]) {
|
|
512
|
+
color: var(--foreground);
|
|
513
|
+
}
|
|
514
|
+
[data-slot='tabs-list'][data-variant='line'][data-orientation='horizontal']
|
|
515
|
+
> [data-slot='tabs-trigger']::after {
|
|
516
|
+
bottom: -1px;
|
|
517
|
+
height: 2px;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/* Every scrollbar, across and down: a thin rounded bar in a faint grey on no
|
|
521
|
+
track, drawn only while its element scrolls or the mouse is near the edge
|
|
522
|
+
it runs along (revealScrollbarsWhileScrolling
|
|
523
|
+
in client/lib/scrollbar.ts marks it `data-scrolling` until it has been
|
|
524
|
+
still for a moment) and transparent otherwise, so showing it moves nothing;
|
|
525
|
+
a little firmer under the pointer. Chromium and Safari draw it through the
|
|
526
|
+
-webkit- pseudo-elements, which allow a thinner bar than
|
|
527
|
+
`scrollbar-width: thin`; they ignore those wherever the standard properties
|
|
528
|
+
are set (scrollbar-color is inherited), so the standard ones are given only
|
|
529
|
+
to a browser without them (Firefox). */
|
|
530
|
+
::-webkit-scrollbar {
|
|
531
|
+
width: 6px;
|
|
532
|
+
height: 6px;
|
|
533
|
+
}
|
|
534
|
+
::-webkit-scrollbar-track,
|
|
535
|
+
::-webkit-scrollbar-corner {
|
|
536
|
+
background: transparent;
|
|
537
|
+
}
|
|
538
|
+
::-webkit-scrollbar-thumb {
|
|
539
|
+
border-radius: 9999px;
|
|
540
|
+
background-color: transparent;
|
|
541
|
+
}
|
|
542
|
+
[data-scrolling]::-webkit-scrollbar-thumb {
|
|
543
|
+
background-color: color-mix(
|
|
544
|
+
in oklab,
|
|
545
|
+
var(--muted-foreground) 20%,
|
|
546
|
+
transparent
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
[data-scrolling]::-webkit-scrollbar-thumb:hover {
|
|
550
|
+
background-color: color-mix(
|
|
551
|
+
in oklab,
|
|
552
|
+
var(--muted-foreground) 30%,
|
|
553
|
+
transparent
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
557
|
+
* {
|
|
558
|
+
scrollbar-width: thin;
|
|
559
|
+
scrollbar-color: transparent transparent;
|
|
560
|
+
}
|
|
561
|
+
[data-scrolling] {
|
|
562
|
+
scrollbar-color: color-mix(
|
|
563
|
+
in oklab,
|
|
564
|
+
var(--muted-foreground) 20%,
|
|
565
|
+
transparent
|
|
566
|
+
)
|
|
567
|
+
transparent;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* A scroller whose bar always shows, thicker, to be dragged: one wider than
|
|
572
|
+
the page, such as a board, which a mouse without a sideways wheel moves
|
|
573
|
+
only by its bar. Plain rules beside the ones above rather than a utility,
|
|
574
|
+
which their unlayered rules would outrank. */
|
|
575
|
+
.scroll-shown::-webkit-scrollbar {
|
|
576
|
+
width: 8px;
|
|
577
|
+
height: 8px;
|
|
578
|
+
}
|
|
579
|
+
.scroll-shown::-webkit-scrollbar-thumb {
|
|
580
|
+
background-color: color-mix(
|
|
581
|
+
in oklab,
|
|
582
|
+
var(--muted-foreground) 25%,
|
|
583
|
+
transparent
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
.scroll-shown::-webkit-scrollbar-thumb:hover {
|
|
587
|
+
background-color: color-mix(
|
|
588
|
+
in oklab,
|
|
589
|
+
var(--muted-foreground) 40%,
|
|
590
|
+
transparent
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
594
|
+
.scroll-shown {
|
|
595
|
+
scrollbar-color: color-mix(
|
|
596
|
+
in oklab,
|
|
597
|
+
var(--muted-foreground) 25%,
|
|
598
|
+
transparent
|
|
599
|
+
)
|
|
600
|
+
transparent;
|
|
601
|
+
}
|
|
366
602
|
}
|
|
@@ -36,7 +36,7 @@ export function ThemeSettings(): ReactElement {
|
|
|
36
36
|
aria-label={title}
|
|
37
37
|
size='icon'
|
|
38
38
|
variant='outline'
|
|
39
|
-
className='size-10 rounded-xl border-border
|
|
39
|
+
className='size-10 rounded-xl border-border bg-background/60 hover:bg-accent/50'
|
|
40
40
|
/>
|
|
41
41
|
}
|
|
42
42
|
>
|
|
@@ -70,13 +70,15 @@ export function ThemeSettings(): ReactElement {
|
|
|
70
70
|
<label
|
|
71
71
|
key={mode}
|
|
72
72
|
className={cn(
|
|
73
|
-
'relative flex min-w-0 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-colors select-none
|
|
73
|
+
'relative flex min-w-0 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-colors select-none',
|
|
74
74
|
isSelected
|
|
75
75
|
? 'bg-background text-foreground shadow-xs'
|
|
76
76
|
: 'text-muted-foreground hover:bg-background/40 hover:text-foreground',
|
|
77
77
|
)}
|
|
78
78
|
>
|
|
79
|
-
|
|
79
|
+
{/* `absolute` over the primitive's `relative`, which would keep
|
|
80
|
+
the hidden radio in the row and push the words aside. */}
|
|
81
|
+
<RadioGroupItem value={mode} className='absolute sr-only' />
|
|
80
82
|
<ModeIcon aria-hidden='true' className='size-3.5 shrink-0' />
|
|
81
83
|
<span className='whitespace-nowrap'>
|
|
82
84
|
{t('appearance.' + mode, {
|
|
@@ -109,18 +111,21 @@ export function ThemeSettings(): ReactElement {
|
|
|
109
111
|
<label
|
|
110
112
|
key={item.id}
|
|
111
113
|
className={cn(
|
|
112
|
-
'group relative flex cursor-pointer flex-col rounded-xl border p-2 text-left transition-all select-none
|
|
114
|
+
'group relative flex cursor-pointer flex-col rounded-xl border p-2 text-left transition-all select-none',
|
|
113
115
|
isSelected
|
|
114
116
|
? 'border-primary ring-2 ring-primary/20 bg-primary/5 shadow-xs'
|
|
115
|
-
: 'border-border
|
|
117
|
+
: 'border-border hover:bg-muted/30',
|
|
116
118
|
)}
|
|
117
119
|
>
|
|
118
|
-
<RadioGroupItem
|
|
120
|
+
<RadioGroupItem
|
|
121
|
+
value={item.id}
|
|
122
|
+
className='absolute sr-only'
|
|
123
|
+
/>
|
|
119
124
|
<div className='mb-2 h-16 w-full'>
|
|
120
125
|
<div
|
|
121
126
|
aria-hidden='true'
|
|
122
127
|
data-theme={item.id}
|
|
123
|
-
className='theme-preview relative flex h-full overflow-hidden rounded-lg border border-border
|
|
128
|
+
className='theme-preview relative flex h-full overflow-hidden rounded-lg border border-border bg-background shadow-2xs'
|
|
124
129
|
>
|
|
125
130
|
{/* Mini Sidebar */}
|
|
126
131
|
<div className='flex w-6 flex-col gap-1 border-r border-sidebar-border/50 bg-sidebar p-1'>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
|
10
10
|
'Courier New', monospace;
|
|
11
11
|
--font-heading: var(--font-sans);
|
|
12
|
-
--spacing: 0.
|
|
12
|
+
--spacing: 0.225rem;
|
|
13
13
|
--text-xs: 0.75rem;
|
|
14
14
|
--text-xs--line-height: calc(1 / 0.75);
|
|
15
15
|
--text-sm: 0.875rem;
|
|
@@ -45,15 +45,11 @@
|
|
|
45
45
|
--shadow-xl:
|
|
46
46
|
0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
47
47
|
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
|
48
|
-
--radius: 0.
|
|
48
|
+
--radius: 0.5rem;
|
|
49
49
|
--background: oklch(0.985 0 0);
|
|
50
50
|
--foreground: oklch(0.16 0 0);
|
|
51
51
|
--card: oklch(1 0 0);
|
|
52
52
|
--card-foreground: oklch(0.16 0 0);
|
|
53
|
-
--overlay-backdrop: rgb(15 23 42 / 0.28);
|
|
54
|
-
--overlay-shadow:
|
|
55
|
-
0 24px 64px -16px rgb(15 23 42 / 0.22),
|
|
56
|
-
0 8px 24px -12px rgb(15 23 42 / 0.16);
|
|
57
53
|
--popover: oklch(1 0 0);
|
|
58
54
|
--popover-foreground: oklch(0.16 0 0);
|
|
59
55
|
--primary: #002ad1;
|
|
@@ -71,8 +67,9 @@
|
|
|
71
67
|
--muted-foreground: oklch(0.52 0 0);
|
|
72
68
|
--accent: rgb(237 242 255);
|
|
73
69
|
--accent-foreground: rgb(0 42 209);
|
|
70
|
+
--hover: color-mix(in srgb, var(--muted) 70%, var(--card));
|
|
74
71
|
--destructive: oklch(0.577 0.245 27.325);
|
|
75
|
-
--border: oklch(0.9 0 0);
|
|
72
|
+
--border: oklch(0.9 0 0 / 60%);
|
|
76
73
|
--input: oklch(0.9 0 0);
|
|
77
74
|
--ring: var(--primary);
|
|
78
75
|
--chart-1: oklch(0.646 0.222 41.116);
|
|
@@ -84,8 +81,8 @@
|
|
|
84
81
|
--sidebar-foreground: var(--card-foreground);
|
|
85
82
|
--sidebar-primary: color-mix(in srgb, var(--primary) 8%, var(--sidebar));
|
|
86
83
|
--sidebar-primary-foreground: var(--primary);
|
|
87
|
-
--sidebar-accent: var(--
|
|
88
|
-
--sidebar-accent-foreground: var(--
|
|
84
|
+
--sidebar-accent: var(--hover);
|
|
85
|
+
--sidebar-accent-foreground: var(--sidebar-foreground);
|
|
89
86
|
--sidebar-border: var(--border);
|
|
90
87
|
--sidebar-ring: var(--ring);
|
|
91
88
|
}
|
|
@@ -96,9 +93,6 @@
|
|
|
96
93
|
--foreground: oklch(0.96 0 0);
|
|
97
94
|
--card: oklch(0.18 0 0);
|
|
98
95
|
--card-foreground: oklch(0.985 0 0);
|
|
99
|
-
--overlay-backdrop: rgb(0 0 0 / 0.58);
|
|
100
|
-
--overlay-shadow:
|
|
101
|
-
0 24px 64px -16px rgb(0 0 0 / 0.6), 0 8px 24px -12px rgb(0 0 0 / 0.4);
|
|
102
96
|
--popover: oklch(0.18 0 0);
|
|
103
97
|
--popover-foreground: oklch(0.985 0 0);
|
|
104
98
|
--primary: #8ca9ff;
|
|
@@ -113,6 +107,7 @@
|
|
|
113
107
|
--muted-foreground: oklch(0.7 0 0);
|
|
114
108
|
--accent: rgb(30 42 72);
|
|
115
109
|
--accent-foreground: rgb(174 194 255);
|
|
110
|
+
--hover: color-mix(in srgb, var(--muted) 70%, var(--card));
|
|
116
111
|
--destructive: oklch(0.704 0.191 22.216);
|
|
117
112
|
--border: oklch(1 0 0 / 10%);
|
|
118
113
|
--input: oklch(1 0 0 / 15%);
|
|
@@ -126,8 +121,8 @@
|
|
|
126
121
|
--sidebar-foreground: var(--card-foreground);
|
|
127
122
|
--sidebar-primary: color-mix(in srgb, var(--primary) 8%, var(--sidebar));
|
|
128
123
|
--sidebar-primary-foreground: var(--primary);
|
|
129
|
-
--sidebar-accent: var(--
|
|
130
|
-
--sidebar-accent-foreground: var(--
|
|
124
|
+
--sidebar-accent: var(--hover);
|
|
125
|
+
--sidebar-accent-foreground: var(--sidebar-foreground);
|
|
131
126
|
--sidebar-border: var(--border);
|
|
132
127
|
--sidebar-ring: var(--ring);
|
|
133
128
|
}
|
|
@@ -51,10 +51,6 @@
|
|
|
51
51
|
--foreground: oklch(0.16 0 0);
|
|
52
52
|
--card: oklch(1 0 0);
|
|
53
53
|
--card-foreground: oklch(0.16 0 0);
|
|
54
|
-
--overlay-backdrop: rgb(15 23 42 / 0.28);
|
|
55
|
-
--overlay-shadow:
|
|
56
|
-
0 24px 64px -16px rgb(15 23 42 / 0.22),
|
|
57
|
-
0 8px 24px -12px rgb(15 23 42 / 0.16);
|
|
58
54
|
--popover: oklch(1 0 0);
|
|
59
55
|
--popover-foreground: oklch(0.16 0 0);
|
|
60
56
|
--primary: #002ad1;
|
|
@@ -72,8 +68,9 @@
|
|
|
72
68
|
--muted-foreground: oklch(0.52 0 0);
|
|
73
69
|
--accent: rgb(237 242 255);
|
|
74
70
|
--accent-foreground: rgb(0 42 209);
|
|
71
|
+
--hover: color-mix(in srgb, var(--muted) 70%, var(--card));
|
|
75
72
|
--destructive: oklch(0.577 0.245 27.325);
|
|
76
|
-
--border: oklch(0.9 0 0);
|
|
73
|
+
--border: oklch(0.9 0 0 / 60%);
|
|
77
74
|
--input: oklch(0.9 0 0);
|
|
78
75
|
--ring: var(--primary);
|
|
79
76
|
--chart-1: oklch(0.646 0.222 41.116);
|
|
@@ -85,8 +82,8 @@
|
|
|
85
82
|
--sidebar-foreground: var(--card-foreground);
|
|
86
83
|
--sidebar-primary: color-mix(in srgb, var(--primary) 8%, var(--sidebar));
|
|
87
84
|
--sidebar-primary-foreground: var(--primary);
|
|
88
|
-
--sidebar-accent: var(--
|
|
89
|
-
--sidebar-accent-foreground: var(--
|
|
85
|
+
--sidebar-accent: var(--hover);
|
|
86
|
+
--sidebar-accent-foreground: var(--sidebar-foreground);
|
|
90
87
|
--sidebar-border: var(--border);
|
|
91
88
|
--sidebar-ring: var(--ring);
|
|
92
89
|
}
|
|
@@ -98,9 +95,6 @@
|
|
|
98
95
|
--foreground: oklch(0.96 0 0);
|
|
99
96
|
--card: oklch(0.18 0 0);
|
|
100
97
|
--card-foreground: oklch(0.985 0 0);
|
|
101
|
-
--overlay-backdrop: rgb(0 0 0 / 0.58);
|
|
102
|
-
--overlay-shadow:
|
|
103
|
-
0 24px 64px -16px rgb(0 0 0 / 0.6), 0 8px 24px -12px rgb(0 0 0 / 0.4);
|
|
104
98
|
--popover: oklch(0.18 0 0);
|
|
105
99
|
--popover-foreground: oklch(0.985 0 0);
|
|
106
100
|
--primary: #8ca9ff;
|
|
@@ -115,6 +109,7 @@
|
|
|
115
109
|
--muted-foreground: oklch(0.7 0 0);
|
|
116
110
|
--accent: rgb(30 42 72);
|
|
117
111
|
--accent-foreground: rgb(174 194 255);
|
|
112
|
+
--hover: color-mix(in srgb, var(--muted) 70%, var(--card));
|
|
118
113
|
--destructive: oklch(0.704 0.191 22.216);
|
|
119
114
|
--border: oklch(1 0 0 / 10%);
|
|
120
115
|
--input: oklch(1 0 0 / 15%);
|
|
@@ -128,8 +123,8 @@
|
|
|
128
123
|
--sidebar-foreground: var(--card-foreground);
|
|
129
124
|
--sidebar-primary: color-mix(in srgb, var(--primary) 8%, var(--sidebar));
|
|
130
125
|
--sidebar-primary-foreground: var(--primary);
|
|
131
|
-
--sidebar-accent: var(--
|
|
132
|
-
--sidebar-accent-foreground: var(--
|
|
126
|
+
--sidebar-accent: var(--hover);
|
|
127
|
+
--sidebar-accent-foreground: var(--sidebar-foreground);
|
|
133
128
|
--sidebar-border: var(--border);
|
|
134
129
|
--sidebar-ring: var(--ring);
|
|
135
130
|
}
|