@autono/open-pages 0.6.0 → 0.8.0
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/dist/{build-6hM9Ga2d.js → build-CCoxDxL-.js} +11 -10
- package/dist/cli/bin.d.ts +1 -1
- package/dist/cli/bin.js +427 -30
- package/dist/{config-CBbwLHOo.js → config-D_U_aCQz.js} +484 -460
- package/dist/{config-C-sasGou.d.ts → config-p-TWNXbv.d.ts} +4 -4
- package/dist/{design-C13iz9_4.js → design-Cpqx7SJs.js} +1 -2
- package/dist/{dev-BO0cHiYy.js → dev-CTrB263u.js} +40 -20
- package/dist/{export-DbVwSmKW.js → export-enKL9R7x.js} +7 -10
- package/dist/index.d.ts +6 -8
- package/dist/index.js +2 -3
- package/dist/locale/index.d.ts +8 -14
- package/dist/locale/index.js +67 -32
- package/dist/{open-pages-plugin-C0poXLsx.js → open-pages-plugin-C8cFhMB9.js} +23 -38
- package/dist/preview-Dmzyd1cf.js +24 -0
- package/dist/{sync-ui-CDydLpY4.js → sync-ui-BzRiAGCK.js} +3 -5
- package/dist/{types-BzxkFHhd.d.ts → types-DDdUFUIS.d.ts} +19 -6
- package/dist/{update-D1U8TVwd.js → update-CWaCKlnl.js} +4 -7
- package/dist/{update-package-D9zws9nc.js → update-package-GaNsVdr8.js} +44 -22
- package/dist/{version-B6AiiGvT.js → version-DZrXLoAD.js} +19 -3
- package/dist/vite/index.d.ts +3 -7
- package/dist/vite/index.js +2 -7
- package/package.json +22 -21
- package/skills/apply-comments/SKILL.md +1 -0
- package/skills/create-page/SKILL.md +5 -1
- package/skills/create-theme/SKILL.md +2 -0
- package/skills/page-authoring/SKILL.md +3 -0
- package/skills/web-design-guidelines/SKILL.md +41 -0
- package/src/app/app.tsx +19 -14
- package/src/app/assets/autono-social.png +0 -0
- package/src/app/assets/autono.svg +7 -0
- package/src/app/components/asset-view.tsx +510 -26
- package/src/app/components/command/command-menu.tsx +5 -1
- package/src/app/components/command/command.tsx +4 -2
- package/src/app/components/command/home-command-menu.tsx +6 -6
- package/src/app/components/icon-tooltip.tsx +24 -15
- package/src/app/components/sidebar/folder-item.tsx +46 -19
- package/src/app/components/sidebar/icon-picker.tsx +1 -1
- package/src/app/components/sidebar/sidebar-footer.tsx +18 -7
- package/src/app/components/sidebar/sidebar.tsx +91 -64
- package/src/app/components/theme-toggle.tsx +4 -5
- package/src/app/components/themes/theme-detail.tsx +6 -3
- package/src/app/components/themes/themes-gallery.tsx +12 -7
- package/src/app/index.html +33 -2
- package/src/app/lib/assets.ts +33 -0
- package/src/app/lib/last-home-location.ts +18 -0
- package/src/app/main.tsx +10 -1
- package/src/app/routes/home-shell.tsx +82 -61
- package/src/app/routes/home.tsx +42 -39
- package/src/app/routes/page.tsx +30 -10
- package/src/app/routes/themes.tsx +6 -8
- package/src/app/styles.css +40 -4
- package/src/app/virtual.d.ts +1 -0
- package/src/locale/en.ts +17 -6
- package/src/locale/ja.ts +18 -6
- package/src/locale/types.ts +17 -4
- package/src/locale/zh-cn.ts +19 -8
- package/src/locale/zh-tw.ts +17 -6
- package/src/shared/update-package.ts +193 -0
- package/workspace/deps.json +1 -1
- package/dist/export-BsK6OCpc.js +0 -4
- package/dist/preview-sOKhzyWB.js +0 -21
- package/dist/sync-Be_hxSOF.js +0 -139
- package/dist/sync-lKEXyPOh.js +0 -3
- package/dist/version-CeffgdfZ.js +0 -3
- package/src/app/components/language-toggle.tsx +0 -46
- package/src/app/favicon.ico +0 -0
|
@@ -31,7 +31,9 @@ function CommandDialog({
|
|
|
31
31
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
32
32
|
<DialogContent
|
|
33
33
|
showCloseButton={false}
|
|
34
|
-
|
|
34
|
+
// ⌘K fires dozens of times a day — opacity-only and fast, no scale
|
|
35
|
+
// theater on a keyboard-triggered surface.
|
|
36
|
+
className="top-[14vh] translate-y-0 gap-0 overflow-hidden p-0 duration-100 data-starting-style:scale-100 data-ending-style:scale-100 sm:max-w-[560px]"
|
|
35
37
|
>
|
|
36
38
|
<DialogTitle className="sr-only">{title}</DialogTitle>
|
|
37
39
|
<DialogDescription className="sr-only">{description}</DialogDescription>
|
|
@@ -107,7 +109,7 @@ function CommandItem({ className, ...props }: React.ComponentProps<typeof Comman
|
|
|
107
109
|
data-slot="command-item"
|
|
108
110
|
className={cn(
|
|
109
111
|
'relative flex cursor-default select-none items-center gap-2 rounded-[5px] px-2 py-1.5 text-[12.5px] outline-none',
|
|
110
|
-
'data-[selected=true]:bg-muted data-[selected=true]:text-foreground',
|
|
112
|
+
'data-[selected=true]:bg-muted data-[selected=true]:text-foreground active:bg-muted/80',
|
|
111
113
|
'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-45',
|
|
112
114
|
"[&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:text-muted-foreground [&_svg:not([class*='opacity'])]:opacity-80",
|
|
113
115
|
className,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Presentation } from 'lucide-react';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { useNavigate } from 'react-router-dom';
|
|
4
4
|
import { useLocale } from '~/lib/use-locale';
|
|
5
5
|
import { usePageTitles } from '~/lib/use-page-titles';
|
|
6
6
|
import { pageIds } from '../../lib/pages';
|
|
7
7
|
import type { Folder } from '../../lib/sdk';
|
|
8
|
-
import { FolderIconChip } from '../sidebar/folder-item';
|
|
8
|
+
import { FolderIconChip, SystemViewIcon } from '../sidebar/folder-item';
|
|
9
9
|
import { ALL_DOCS_ID, ASSETS_ID, DRAFT_ID, THEMES_ID } from '../sidebar/sidebar';
|
|
10
10
|
import { type CommandGroupSpec, CommandMenu, type CommandSpec } from './command-menu';
|
|
11
11
|
|
|
@@ -39,14 +39,14 @@ export function HomeCommandMenu({
|
|
|
39
39
|
{
|
|
40
40
|
id: `view-${ALL_DOCS_ID}`,
|
|
41
41
|
label: t.home.pages,
|
|
42
|
-
icon: <
|
|
42
|
+
icon: <SystemViewIcon kind="all" />,
|
|
43
43
|
keywords: ['all', 'pages'],
|
|
44
44
|
run: () => onSelectView(ALL_DOCS_ID),
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
id: `view-${DRAFT_ID}`,
|
|
48
48
|
label: t.home.draft,
|
|
49
|
-
icon: <
|
|
49
|
+
icon: <SystemViewIcon kind="draft" />,
|
|
50
50
|
keywords: ['draft', 'unsorted'],
|
|
51
51
|
run: () => onSelectView(DRAFT_ID),
|
|
52
52
|
},
|
|
@@ -63,7 +63,7 @@ export function HomeCommandMenu({
|
|
|
63
63
|
{
|
|
64
64
|
id: `view-${THEMES_ID}`,
|
|
65
65
|
label: t.home.themes,
|
|
66
|
-
icon: <
|
|
66
|
+
icon: <SystemViewIcon kind="themes" />,
|
|
67
67
|
keywords: ['themes', 'design'],
|
|
68
68
|
run: () => onSelectView(THEMES_ID),
|
|
69
69
|
},
|
|
@@ -72,7 +72,7 @@ export function HomeCommandMenu({
|
|
|
72
72
|
navigation.push({
|
|
73
73
|
id: `view-${ASSETS_ID}`,
|
|
74
74
|
label: t.home.assets,
|
|
75
|
-
icon: <
|
|
75
|
+
icon: <SystemViewIcon kind="assets" />,
|
|
76
76
|
keywords: ['assets', 'images', 'files'],
|
|
77
77
|
run: () => onSelectView(ASSETS_ID),
|
|
78
78
|
});
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
|
-
import { Tooltip, TooltipContent
|
|
3
|
+
import { Tooltip, TooltipContent } from '~/components/ui/tooltip';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tooltip trigger for a button that also opens a menu. The `ui/tooltip` wrapper
|
|
7
|
+
* is a plain function component, so on React 18 it swallows the ref that
|
|
8
|
+
* `DropdownMenuTrigger` hands to its `render` element and the menu never opens —
|
|
9
|
+
* the primitive forwards it.
|
|
10
|
+
*/
|
|
11
|
+
export const MenuTooltipTrigger = TooltipPrimitive.Trigger;
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* Shared hover label for the toolbar icon buttons. `children` supplies the
|
|
6
15
|
* `TooltipTrigger`; a button that also opens a menu has to compose it inward
|
|
7
|
-
* (`<DropdownMenuTrigger render={<
|
|
16
|
+
* (`<DropdownMenuTrigger render={<MenuTooltipTrigger />}>`) — the other nesting
|
|
8
17
|
* order swallows the trigger ref and the tooltip never opens.
|
|
9
18
|
*/
|
|
10
19
|
export function IconTooltip({
|
|
@@ -16,19 +25,19 @@ export function IconTooltip({
|
|
|
16
25
|
shortcut?: string;
|
|
17
26
|
children: ReactNode;
|
|
18
27
|
}) {
|
|
28
|
+
// No local provider — tooltips join the app-level group so adjacent
|
|
29
|
+
// toolbar buttons open instantly once one tooltip is showing.
|
|
19
30
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
</Tooltip>
|
|
32
|
-
</TooltipProvider>
|
|
31
|
+
<Tooltip>
|
|
32
|
+
{children}
|
|
33
|
+
<TooltipContent side="bottom" sideOffset={6} className="flex items-center gap-1.5">
|
|
34
|
+
{label}
|
|
35
|
+
{shortcut && (
|
|
36
|
+
<kbd className="rounded-[3px] bg-background/18 px-1 py-0.5 font-mono text-[9.5px] tracking-[0.04em] text-background/85">
|
|
37
|
+
{shortcut}
|
|
38
|
+
</kbd>
|
|
39
|
+
)}
|
|
40
|
+
</TooltipContent>
|
|
41
|
+
</Tooltip>
|
|
33
42
|
);
|
|
34
43
|
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
FolderOpen,
|
|
3
|
+
LayoutGrid,
|
|
4
|
+
type LucideIcon,
|
|
5
|
+
MoreHorizontal,
|
|
6
|
+
Palette,
|
|
7
|
+
Pencil,
|
|
8
|
+
PenLine,
|
|
9
|
+
Trash2,
|
|
10
|
+
} from 'lucide-react';
|
|
2
11
|
import { useEffect, useRef, useState } from 'react';
|
|
3
12
|
import {
|
|
4
13
|
DropdownMenu,
|
|
@@ -33,6 +42,24 @@ function useDocDragActive() {
|
|
|
33
42
|
return active;
|
|
34
43
|
}
|
|
35
44
|
|
|
45
|
+
export type SystemViewKind = 'all' | 'draft' | 'themes' | 'assets';
|
|
46
|
+
|
|
47
|
+
const SYSTEM_VIEW_ICONS: Record<SystemViewKind, LucideIcon> = {
|
|
48
|
+
all: LayoutGrid,
|
|
49
|
+
draft: PenLine,
|
|
50
|
+
themes: Palette,
|
|
51
|
+
assets: FolderOpen,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function SystemViewIcon({ kind, className }: { kind: SystemViewKind; className?: string }) {
|
|
55
|
+
const Icon = SYSTEM_VIEW_ICONS[kind];
|
|
56
|
+
return (
|
|
57
|
+
<span aria-hidden className={cn('flex size-5 shrink-0 items-center justify-center', className)}>
|
|
58
|
+
<Icon className="size-4" strokeWidth={1.75} />
|
|
59
|
+
</span>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
export function FolderIconChip({ icon, className }: { icon: FolderIcon; className?: string }) {
|
|
37
64
|
if (icon.type === 'emoji') {
|
|
38
65
|
return (
|
|
@@ -126,16 +153,12 @@ export function FolderItem({
|
|
|
126
153
|
onDropDoc(pageId);
|
|
127
154
|
};
|
|
128
155
|
|
|
129
|
-
const
|
|
130
|
-
row.kind === '
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
? { type: 'emoji', value: '🎨' }
|
|
136
|
-
: row.kind === 'assets'
|
|
137
|
-
? { type: 'emoji', value: '🗂️' }
|
|
138
|
-
: row.folder.icon;
|
|
156
|
+
const chip =
|
|
157
|
+
row.kind === 'folder' ? (
|
|
158
|
+
<FolderIconChip icon={row.folder.icon} />
|
|
159
|
+
) : (
|
|
160
|
+
<SystemViewIcon kind={row.kind} className={cn(!selected && 'text-muted-foreground')} />
|
|
161
|
+
);
|
|
139
162
|
const label =
|
|
140
163
|
row.kind === 'all'
|
|
141
164
|
? t.home.pages
|
|
@@ -158,13 +181,13 @@ export function FolderItem({
|
|
|
158
181
|
// biome-ignore lint/a11y/noStaticElementInteractions: drag-and-drop target wraps interactive children
|
|
159
182
|
<div
|
|
160
183
|
className={cn(
|
|
161
|
-
'group relative flex items-center gap-2.5 rounded-[5px] px-2 py-[5px] text-[12.5px] transition-
|
|
184
|
+
'group relative flex items-center gap-2.5 rounded-[5px] px-2 py-[5px] text-[12.5px] transition-[background-color,color,scale] duration-150',
|
|
162
185
|
selected
|
|
163
|
-
? 'bg-
|
|
186
|
+
? 'bg-background font-medium text-foreground shadow-edge ring-1 ring-foreground/[0.06]'
|
|
164
187
|
: 'text-foreground/70 hover:bg-muted/60 hover:text-foreground',
|
|
165
188
|
docDragActive && acceptsDocDrop && !dragOver && 'ring-1 ring-foreground/10',
|
|
166
189
|
dragOver &&
|
|
167
|
-
'bg-brand/10 text-foreground ring-1 ring-brand ring-offset-1 ring-offset-sidebar motion-safe:scale-[1.01]
|
|
190
|
+
'bg-brand/10 text-foreground ring-1 ring-brand ring-offset-1 ring-offset-sidebar motion-safe:scale-[1.01]',
|
|
168
191
|
)}
|
|
169
192
|
onDragEnter={handleDragEnter}
|
|
170
193
|
onDragOver={handleDragOver}
|
|
@@ -177,11 +200,11 @@ export function FolderItem({
|
|
|
177
200
|
render={
|
|
178
201
|
<button
|
|
179
202
|
type="button"
|
|
180
|
-
className="flex size-5 shrink-0 items-center justify-center rounded transition-transform hover:scale-110"
|
|
203
|
+
className="flex size-5 shrink-0 items-center justify-center rounded outline-none motion-safe:transition-transform motion-safe:duration-150 hover:scale-110 active:scale-95 focus-visible:ring-1 focus-visible:ring-brand"
|
|
181
204
|
aria-label={t.home.changeIcon}
|
|
182
205
|
onClick={(e) => e.stopPropagation()}
|
|
183
206
|
>
|
|
184
|
-
|
|
207
|
+
{chip}
|
|
185
208
|
</button>
|
|
186
209
|
}
|
|
187
210
|
/>
|
|
@@ -196,7 +219,7 @@ export function FolderItem({
|
|
|
196
219
|
aria-label={label}
|
|
197
220
|
className="flex size-5 shrink-0 items-center justify-center"
|
|
198
221
|
>
|
|
199
|
-
|
|
222
|
+
{chip}
|
|
200
223
|
</button>
|
|
201
224
|
)}
|
|
202
225
|
|
|
@@ -217,7 +240,11 @@ export function FolderItem({
|
|
|
217
240
|
className="min-w-0 flex-1 rounded-[3px] bg-card px-1 text-[12.5px] outline-none ring-1 ring-foreground/20"
|
|
218
241
|
/>
|
|
219
242
|
) : (
|
|
220
|
-
<button
|
|
243
|
+
<button
|
|
244
|
+
type="button"
|
|
245
|
+
onClick={onSelect}
|
|
246
|
+
className="min-w-0 flex-1 truncate rounded-[3px] text-left outline-none focus-visible:ring-1 focus-visible:ring-brand"
|
|
247
|
+
>
|
|
221
248
|
{label}
|
|
222
249
|
</button>
|
|
223
250
|
)}
|
|
@@ -240,7 +267,7 @@ export function FolderItem({
|
|
|
240
267
|
<button
|
|
241
268
|
type="button"
|
|
242
269
|
onClick={(e) => e.stopPropagation()}
|
|
243
|
-
className="absolute right-2 top-1/2 size-5 -translate-y-1/2 rounded opacity-0 transition-opacity hover:bg-foreground/10 group-hover:opacity-100 aria-expanded:opacity-100"
|
|
270
|
+
className="absolute right-2 top-1/2 size-5 -translate-y-1/2 rounded opacity-0 outline-none transition-opacity duration-150 hover:bg-foreground/10 group-hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-1 focus-visible:ring-brand aria-expanded:opacity-100"
|
|
244
271
|
aria-label={t.home.folderActions}
|
|
245
272
|
>
|
|
246
273
|
<MoreHorizontal className="mx-auto size-3.5" />
|
|
@@ -49,7 +49,7 @@ export function IconPicker({
|
|
|
49
49
|
key={c}
|
|
50
50
|
type="button"
|
|
51
51
|
onClick={() => onChange({ type: 'color', value: c })}
|
|
52
|
-
className="size-6 rounded-[4px] ring-1 ring-foreground/10 shadow-[inset_0_1px_0_oklch(1_0_0/0.18)] transition-transform hover:scale-110"
|
|
52
|
+
className="size-6 rounded-[4px] ring-1 ring-foreground/10 shadow-[inset_0_1px_0_oklch(1_0_0/0.18)] outline-none motion-safe:transition-transform motion-safe:duration-100 hover:scale-110 active:scale-95 focus-visible:ring-2 focus-visible:ring-brand"
|
|
53
53
|
style={{ background: c }}
|
|
54
54
|
aria-label={c}
|
|
55
55
|
/>
|
|
@@ -2,6 +2,7 @@ import config from 'virtual:open-pages/config';
|
|
|
2
2
|
import { Loader2, RefreshCw, RotateCw } from 'lucide-react';
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
4
|
import { toast } from 'sonner';
|
|
5
|
+
import { ThemeToggle } from '~/components/theme-toggle';
|
|
5
6
|
import { Button } from '~/components/ui/button';
|
|
6
7
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui/tooltip';
|
|
7
8
|
import { format, useLocale } from '~/lib/use-locale';
|
|
@@ -53,15 +54,22 @@ export function SidebarFooter() {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
const versionRow = (
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const versionRow = update?.latest ? (
|
|
58
|
+
// Real button: the tooltip holds actionable controls, so keyboard users
|
|
59
|
+
// must be able to reach and open it.
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
className="inline-flex cursor-default items-center gap-1.5 rounded-[3px] outline-none focus-visible:ring-1 focus-visible:ring-brand"
|
|
63
|
+
>
|
|
64
|
+
<span className="size-1.5 rounded-full bg-brand" aria-hidden />
|
|
59
65
|
{label}
|
|
60
|
-
</
|
|
66
|
+
</button>
|
|
67
|
+
) : (
|
|
68
|
+
<span className="inline-flex cursor-default items-center gap-1.5">{label}</span>
|
|
61
69
|
);
|
|
62
70
|
|
|
63
71
|
return (
|
|
64
|
-
<div className="
|
|
72
|
+
<div className="flex items-center justify-between gap-2 py-1.5 pr-2 pl-4 text-[11px] text-muted-foreground/70 tabular-nums">
|
|
65
73
|
{update?.latest ? (
|
|
66
74
|
<TooltipProvider delay={200}>
|
|
67
75
|
<Tooltip
|
|
@@ -93,7 +101,7 @@ export function SidebarFooter() {
|
|
|
93
101
|
onClick={restartServer}
|
|
94
102
|
>
|
|
95
103
|
{restarting ? (
|
|
96
|
-
<Loader2 className="animate-spin" aria-hidden />
|
|
104
|
+
<Loader2 className="animate-spin motion-reduce:animate-none" aria-hidden />
|
|
97
105
|
) : (
|
|
98
106
|
<RotateCw aria-hidden />
|
|
99
107
|
)}
|
|
@@ -115,7 +123,7 @@ export function SidebarFooter() {
|
|
|
115
123
|
onClick={updatePackage}
|
|
116
124
|
>
|
|
117
125
|
{isUpdating ? (
|
|
118
|
-
<Loader2 className="animate-spin" aria-hidden />
|
|
126
|
+
<Loader2 className="animate-spin motion-reduce:animate-none" aria-hidden />
|
|
119
127
|
) : (
|
|
120
128
|
<RefreshCw aria-hidden />
|
|
121
129
|
)}
|
|
@@ -134,6 +142,9 @@ export function SidebarFooter() {
|
|
|
134
142
|
) : (
|
|
135
143
|
versionRow
|
|
136
144
|
)}
|
|
145
|
+
<div className="flex shrink-0 items-center">
|
|
146
|
+
<ThemeToggle />
|
|
147
|
+
</div>
|
|
137
148
|
</div>
|
|
138
149
|
);
|
|
139
150
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { Plus } from 'lucide-react';
|
|
1
|
+
import { Plus, Search } from 'lucide-react';
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { toast } from 'sonner';
|
|
4
|
-
import
|
|
5
|
-
import { ThemeToggle } from '~/components/theme-toggle';
|
|
4
|
+
import logo from '~/assets/autono.svg';
|
|
6
5
|
import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover';
|
|
7
6
|
import type { Folder, FolderIcon } from '~/lib/sdk';
|
|
8
7
|
import { format, useLocale } from '~/lib/use-locale';
|
|
9
8
|
import { cn } from '~/lib/utils';
|
|
10
|
-
import {
|
|
9
|
+
import { COMMAND_MENU_SHORTCUT } from '../command/command-menu';
|
|
11
10
|
import { FolderIconChip, FolderItem } from './folder-item';
|
|
12
11
|
import { IconPicker, PRESET_COLORS } from './icon-picker';
|
|
13
12
|
import { SidebarFooter } from './sidebar-footer';
|
|
@@ -129,17 +128,42 @@ export function Sidebar({
|
|
|
129
128
|
}, [creating]);
|
|
130
129
|
|
|
131
130
|
return (
|
|
132
|
-
<aside className="relative flex h-full w-[16.5rem] shrink-0 flex-col
|
|
133
|
-
<div className="flex items-center
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
<aside className="group/side relative flex h-full w-[16.5rem] shrink-0 flex-col bg-sidebar text-sidebar-foreground">
|
|
132
|
+
<div className="flex items-center gap-2.5 px-4 pt-4 pb-3">
|
|
133
|
+
<img
|
|
134
|
+
src={logo}
|
|
135
|
+
alt=""
|
|
136
|
+
aria-hidden
|
|
137
|
+
draggable={false}
|
|
138
|
+
className="size-6 shrink-0 select-none rounded-[6px] ring-1 ring-foreground/10"
|
|
139
|
+
/>
|
|
140
|
+
<h1 className="font-heading text-[13.5px] font-semibold tracking-tight">
|
|
141
|
+
{t.home.appTitle}
|
|
142
|
+
</h1>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div className="px-2 pb-3">
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={onOpenCommandMenu}
|
|
149
|
+
aria-label={t.commandMenu.triggerAria}
|
|
150
|
+
aria-haspopup="dialog"
|
|
151
|
+
className={cn(
|
|
152
|
+
'flex h-8 w-full items-center gap-2 rounded-[6px] border border-border bg-card px-2.5 text-[12.5px] text-muted-foreground shadow-edge outline-none',
|
|
153
|
+
'transition-[border-color,color,background-color] duration-100',
|
|
154
|
+
'hover:border-foreground/20 hover:text-foreground',
|
|
155
|
+
'focus-visible:border-foreground/30 focus-visible:ring-2 focus-visible:ring-ring/30',
|
|
156
|
+
)}
|
|
157
|
+
>
|
|
158
|
+
<Search className="size-3.5 shrink-0" aria-hidden />
|
|
159
|
+
<span className="flex-1 truncate text-left">{t.commandMenu.triggerTooltip}</span>
|
|
160
|
+
<kbd className="rounded-[4px] border border-hairline bg-muted/60 px-1 py-px font-mono text-[9.5px] leading-none tracking-[0.04em] text-muted-foreground">
|
|
161
|
+
{COMMAND_MENU_SHORTCUT}
|
|
162
|
+
</kbd>
|
|
163
|
+
</button>
|
|
140
164
|
</div>
|
|
141
165
|
|
|
142
|
-
<div className="px-2">
|
|
166
|
+
<div className="space-y-0.5 px-2">
|
|
143
167
|
<FolderItem
|
|
144
168
|
row={{ kind: 'all' }}
|
|
145
169
|
count={allCount}
|
|
@@ -165,12 +189,27 @@ export function Sidebar({
|
|
|
165
189
|
)}
|
|
166
190
|
</div>
|
|
167
191
|
|
|
168
|
-
<div className="mt-5 flex items-center
|
|
192
|
+
<div className="mt-5 flex h-6 items-center justify-between pr-2.5 pl-4 pb-0.5">
|
|
169
193
|
<span className="eyebrow">{t.home.folders}</span>
|
|
170
|
-
|
|
194
|
+
{import.meta.env.DEV && (
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
onClick={startCreating}
|
|
198
|
+
aria-label={t.home.newFolder}
|
|
199
|
+
title={t.home.newFolder}
|
|
200
|
+
className={cn(
|
|
201
|
+
'flex size-5 items-center justify-center rounded-[4px] text-muted-foreground/70 opacity-0 outline-none',
|
|
202
|
+
'transition-[opacity,background-color,color,scale] duration-100',
|
|
203
|
+
'hover:bg-muted hover:text-foreground active:scale-90',
|
|
204
|
+
'group-hover/side:opacity-100 focus-visible:opacity-100 focus-visible:ring-1 focus-visible:ring-brand',
|
|
205
|
+
)}
|
|
206
|
+
>
|
|
207
|
+
<Plus className="size-3.5" />
|
|
208
|
+
</button>
|
|
209
|
+
)}
|
|
171
210
|
</div>
|
|
172
211
|
|
|
173
|
-
<div className="flex-1 overflow-y-auto px-2 pb-2">
|
|
212
|
+
<div className="flex-1 space-y-0.5 overflow-y-auto px-2 pb-2">
|
|
174
213
|
<FolderItem
|
|
175
214
|
row={{ kind: 'draft' }}
|
|
176
215
|
count={countFor(null)}
|
|
@@ -246,57 +285,45 @@ export function Sidebar({
|
|
|
246
285
|
);
|
|
247
286
|
})}
|
|
248
287
|
|
|
249
|
-
{import.meta.env.DEV &&
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
>
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
/>
|
|
267
|
-
<PopoverContent side="right" align="start" className="w-auto p-2">
|
|
268
|
-
<IconPicker value={newIcon} onChange={setNewIcon} />
|
|
269
|
-
</PopoverContent>
|
|
270
|
-
</Popover>
|
|
271
|
-
<input
|
|
272
|
-
ref={inputRef}
|
|
273
|
-
value={newName}
|
|
274
|
-
onChange={(e) => setNewName(e.target.value)}
|
|
275
|
-
onKeyDown={(e) => {
|
|
276
|
-
if (e.nativeEvent.isComposing) return;
|
|
277
|
-
if (e.key === 'Enter') commitCreate();
|
|
278
|
-
if (e.key === 'Escape') exitCreate();
|
|
279
|
-
}}
|
|
280
|
-
placeholder={t.home.folderName}
|
|
281
|
-
maxLength={40}
|
|
282
|
-
className="min-w-0 flex-1 bg-transparent text-[12.5px] outline-none placeholder:text-muted-foreground/60"
|
|
288
|
+
{import.meta.env.DEV && creating && (
|
|
289
|
+
<div
|
|
290
|
+
data-folder-create
|
|
291
|
+
className="mt-1 flex items-center gap-2.5 rounded-[5px] border border-dashed border-foreground/30 bg-card px-2 py-[5px]"
|
|
292
|
+
>
|
|
293
|
+
<Popover open={iconOpen} onOpenChange={setIconOpen}>
|
|
294
|
+
<PopoverTrigger
|
|
295
|
+
render={
|
|
296
|
+
<button
|
|
297
|
+
type="button"
|
|
298
|
+
className="flex size-5 shrink-0 items-center justify-center rounded outline-none motion-safe:transition-transform motion-safe:duration-150 hover:scale-110 active:scale-95 focus-visible:ring-1 focus-visible:ring-brand"
|
|
299
|
+
aria-label={t.home.pickIcon}
|
|
300
|
+
>
|
|
301
|
+
<FolderIconChip icon={newIcon} />
|
|
302
|
+
</button>
|
|
303
|
+
}
|
|
283
304
|
/>
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
<PopoverContent side="right" align="start" className="w-auto p-2">
|
|
306
|
+
<IconPicker value={newIcon} onChange={setNewIcon} />
|
|
307
|
+
</PopoverContent>
|
|
308
|
+
</Popover>
|
|
309
|
+
<input
|
|
310
|
+
ref={inputRef}
|
|
311
|
+
value={newName}
|
|
312
|
+
onChange={(e) => setNewName(e.target.value)}
|
|
313
|
+
onKeyDown={(e) => {
|
|
314
|
+
if (e.nativeEvent.isComposing) return;
|
|
315
|
+
if (e.key === 'Enter') commitCreate();
|
|
316
|
+
if (e.key === 'Escape') exitCreate();
|
|
317
|
+
}}
|
|
318
|
+
placeholder={t.home.folderName}
|
|
319
|
+
maxLength={40}
|
|
320
|
+
className="min-w-0 flex-1 bg-transparent text-[12.5px] outline-none placeholder:text-muted-foreground/60"
|
|
321
|
+
/>
|
|
322
|
+
</div>
|
|
323
|
+
)}
|
|
295
324
|
</div>
|
|
296
325
|
|
|
297
|
-
<
|
|
298
|
-
<SidebarFooter />
|
|
299
|
-
</div>
|
|
326
|
+
<SidebarFooter />
|
|
300
327
|
</aside>
|
|
301
328
|
);
|
|
302
329
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Monitor, Moon, Sun } from 'lucide-react';
|
|
2
2
|
import { useTheme } from 'next-themes';
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
-
import { IconTooltip } from '~/components/icon-tooltip';
|
|
4
|
+
import { IconTooltip, MenuTooltipTrigger } from '~/components/icon-tooltip';
|
|
5
5
|
import { buttonVariants } from '~/components/ui/button';
|
|
6
6
|
import {
|
|
7
7
|
DropdownMenu,
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
DropdownMenuItem,
|
|
10
10
|
DropdownMenuTrigger,
|
|
11
11
|
} from '~/components/ui/dropdown-menu';
|
|
12
|
-
import { TooltipTrigger } from '~/components/ui/tooltip';
|
|
13
12
|
import { useLocale } from '~/lib/use-locale';
|
|
14
13
|
import { cn } from '~/lib/utils';
|
|
15
14
|
|
|
@@ -27,15 +26,15 @@ export function ThemeToggle() {
|
|
|
27
26
|
<IconTooltip label={t.themeToggle.title}>
|
|
28
27
|
<DropdownMenuTrigger
|
|
29
28
|
render={
|
|
30
|
-
<
|
|
29
|
+
<MenuTooltipTrigger
|
|
31
30
|
type="button"
|
|
32
31
|
aria-label={t.themeToggle.toggleAria}
|
|
33
32
|
className={cn(buttonVariants({ variant: 'ghost', size: 'icon-sm' }), 'relative')}
|
|
34
33
|
/>
|
|
35
34
|
}
|
|
36
35
|
>
|
|
37
|
-
<Sun className="size-3.5 scale-100 rotate-0 transition-
|
|
38
|
-
<Moon className="absolute size-3.5 scale-
|
|
36
|
+
<Sun className="size-3.5 scale-100 rotate-0 opacity-100 ease-swift motion-safe:transition-[opacity,scale,rotate] motion-safe:duration-200 dark:scale-75 dark:-rotate-90 dark:opacity-0" />
|
|
37
|
+
<Moon className="absolute size-3.5 scale-75 rotate-90 opacity-0 ease-swift motion-safe:transition-[opacity,scale,rotate] motion-safe:duration-200 dark:scale-100 dark:rotate-0 dark:opacity-100" />
|
|
39
38
|
</DropdownMenuTrigger>
|
|
40
39
|
</IconTooltip>
|
|
41
40
|
<DropdownMenuContent align="end" className="min-w-[140px]">
|
|
@@ -48,7 +48,7 @@ export function ThemeDetail({ themeId, onBack }: { themeId: string; onBack: () =
|
|
|
48
48
|
</div>
|
|
49
49
|
|
|
50
50
|
<header className="flex flex-wrap items-baseline gap-3">
|
|
51
|
-
<h2 className="font-heading text-[
|
|
51
|
+
<h2 className="font-heading text-[19px] font-semibold leading-none tracking-[-0.015em] md:text-[21px]">
|
|
52
52
|
{theme.name}
|
|
53
53
|
</h2>
|
|
54
54
|
{theme.description ? (
|
|
@@ -156,8 +156,11 @@ function ThemeDocCard({ id }: { id: string }) {
|
|
|
156
156
|
const displayTitle = page?.meta?.title ?? id;
|
|
157
157
|
|
|
158
158
|
return (
|
|
159
|
-
<Link
|
|
160
|
-
|
|
159
|
+
<Link
|
|
160
|
+
to={`/p/${id}`}
|
|
161
|
+
className="group block rounded-[6px] outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
162
|
+
>
|
|
163
|
+
<div className="relative aspect-video overflow-hidden rounded-[6px] border border-hairline bg-card shadow-edge ring-1 ring-foreground/[0.04] group-hover:shadow-floating group-hover:ring-foreground/20 group-active:scale-[0.99] motion-safe:transition-[box-shadow,--tw-ring-color,scale] motion-safe:duration-200">
|
|
161
164
|
<div className="h-full w-full motion-safe:transition-transform motion-safe:duration-300 motion-safe:group-hover:scale-[1.03]">
|
|
162
165
|
<PageThumb source={{ pageId: id, kind: pageKinds[id] }} title={displayTitle} />
|
|
163
166
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Palette } from 'lucide-react';
|
|
1
2
|
import { format, useLocale } from '~/lib/use-locale';
|
|
2
3
|
import { PageThumb } from '../../lib/page-thumb';
|
|
3
4
|
import { type Theme, themes } from '../../lib/themes';
|
|
@@ -11,8 +12,12 @@ export function ThemesGallery({ onOpen }: { onOpen: (id: string) => void }) {
|
|
|
11
12
|
|
|
12
13
|
return (
|
|
13
14
|
<ul className="grid grid-cols-[repeat(auto-fill,minmax(min(240px,100%),1fr))] gap-x-6 gap-y-9 md:grid-cols-[repeat(auto-fill,minmax(340px,1fr))]">
|
|
14
|
-
{themes.map((theme) => (
|
|
15
|
-
<li
|
|
15
|
+
{themes.map((theme, i) => (
|
|
16
|
+
<li
|
|
17
|
+
key={theme.id}
|
|
18
|
+
className="rise-in"
|
|
19
|
+
style={{ animationDelay: `${Math.min(i, 11) * 30}ms` }}
|
|
20
|
+
>
|
|
16
21
|
<ThemeCard
|
|
17
22
|
theme={theme}
|
|
18
23
|
onOpen={() => onOpen(theme.id)}
|
|
@@ -38,9 +43,9 @@ function ThemeCard({
|
|
|
38
43
|
type="button"
|
|
39
44
|
onClick={onOpen}
|
|
40
45
|
aria-label={ariaLabel}
|
|
41
|
-
className="group block w-full text-left focus-visible:
|
|
46
|
+
className="group block w-full rounded-[6px] text-left outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
42
47
|
>
|
|
43
|
-
<div className="relative aspect-video overflow-hidden rounded-[6px] border border-hairline bg-card shadow-edge ring-1 ring-foreground/[0.04] group-hover:shadow-floating group-hover:ring-foreground/20 motion-safe:transition-[box-shadow,--tw-ring-color] motion-safe:duration-200">
|
|
48
|
+
<div className="relative aspect-video overflow-hidden rounded-[6px] border border-hairline bg-card shadow-edge ring-1 ring-foreground/[0.04] group-hover:shadow-floating group-hover:ring-foreground/20 group-active:scale-[0.99] motion-safe:transition-[box-shadow,--tw-ring-color,scale] motion-safe:duration-200">
|
|
44
49
|
<ThemePreview theme={theme} />
|
|
45
50
|
</div>
|
|
46
51
|
<div className="mt-3">
|
|
@@ -85,10 +90,10 @@ function NoDemoState() {
|
|
|
85
90
|
function ThemesEmptyState() {
|
|
86
91
|
const t = useLocale();
|
|
87
92
|
return (
|
|
88
|
-
<div className="rounded-[
|
|
93
|
+
<div className="rounded-[8px] border border-dashed border-border px-8 py-20">
|
|
89
94
|
<div className="mx-auto flex max-w-md flex-col items-center text-center">
|
|
90
|
-
<
|
|
91
|
-
<p className="mt-
|
|
95
|
+
<Palette className="size-5 text-muted-foreground/60" aria-hidden />
|
|
96
|
+
<p className="mt-4 font-heading text-[14px] font-semibold tracking-tight">
|
|
92
97
|
{t.themes.noThemesTitle}
|
|
93
98
|
</p>
|
|
94
99
|
<p className="mt-1.5 text-[13px] leading-relaxed text-muted-foreground">
|