@autono/open-pages 0.1.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.
Files changed (110) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +108 -0
  3. package/bin.js +2 -0
  4. package/dist/build-TP72kiw7.js +35 -0
  5. package/dist/cli/bin.d.ts +1 -0
  6. package/dist/cli/bin.js +100 -0
  7. package/dist/config-C9or4m4J.d.ts +23 -0
  8. package/dist/config-L0IbktJ0.js +3135 -0
  9. package/dist/design-C13iz9_4.js +35 -0
  10. package/dist/dev-I2IyEh5W.js +97 -0
  11. package/dist/export-B-kBRWB1.js +4 -0
  12. package/dist/export-ic28osQP.js +131 -0
  13. package/dist/index.d.ts +56 -0
  14. package/dist/index.js +3 -0
  15. package/dist/locale/index.d.ts +24 -0
  16. package/dist/locale/index.js +881 -0
  17. package/dist/open-pages-plugin-D_DN_zjh.js +632 -0
  18. package/dist/preview-BrDuvgAN.js +21 -0
  19. package/dist/sync-Be_hxSOF.js +139 -0
  20. package/dist/sync-lKEXyPOh.js +3 -0
  21. package/dist/types-C5NCdDT-.d.ts +219 -0
  22. package/dist/update-CV62NhBT.js +37 -0
  23. package/dist/update-package-Cxt57pz7.js +152 -0
  24. package/dist/version-B6AiiGvT.js +26 -0
  25. package/dist/version-CeffgdfZ.js +3 -0
  26. package/dist/vite/index.d.ts +14 -0
  27. package/dist/vite/index.js +7 -0
  28. package/env.d.ts +59 -0
  29. package/package.json +106 -0
  30. package/skills/apply-comments/SKILL.md +86 -0
  31. package/skills/create-page/SKILL.md +92 -0
  32. package/skills/create-theme/SKILL.md +223 -0
  33. package/skills/current-page/SKILL.md +106 -0
  34. package/skills/page-authoring/SKILL.md +148 -0
  35. package/skills/page-authoring/references/assets-and-fonts.md +85 -0
  36. package/skills/page-authoring/references/html-pages.md +65 -0
  37. package/skills/page-authoring/references/interactivity.md +77 -0
  38. package/skills/page-authoring/references/layout-and-responsive.md +71 -0
  39. package/skills/page-authoring/references/typography-and-color.md +60 -0
  40. package/src/app/app.tsx +45 -0
  41. package/src/app/components/asset-view.tsx +1665 -0
  42. package/src/app/components/command/command-menu.tsx +227 -0
  43. package/src/app/components/command/command.tsx +142 -0
  44. package/src/app/components/command/home-command-menu.tsx +96 -0
  45. package/src/app/components/icon-tooltip.tsx +34 -0
  46. package/src/app/components/language-toggle.tsx +46 -0
  47. package/src/app/components/sidebar/folder-item.tsx +269 -0
  48. package/src/app/components/sidebar/icon-picker.tsx +61 -0
  49. package/src/app/components/sidebar/sidebar-footer.tsx +139 -0
  50. package/src/app/components/sidebar/sidebar.tsx +302 -0
  51. package/src/app/components/theme-toggle.tsx +66 -0
  52. package/src/app/components/themes/theme-detail.tsx +226 -0
  53. package/src/app/components/themes/themes-gallery.tsx +104 -0
  54. package/src/app/components/ui/badge.tsx +48 -0
  55. package/src/app/components/ui/button.tsx +93 -0
  56. package/src/app/components/ui/card.tsx +92 -0
  57. package/src/app/components/ui/context-menu.tsx +247 -0
  58. package/src/app/components/ui/dialog.tsx +155 -0
  59. package/src/app/components/ui/dropdown-menu.tsx +267 -0
  60. package/src/app/components/ui/input.tsx +25 -0
  61. package/src/app/components/ui/label.tsx +21 -0
  62. package/src/app/components/ui/popover.tsx +82 -0
  63. package/src/app/components/ui/progress.tsx +33 -0
  64. package/src/app/components/ui/scroll-area.tsx +53 -0
  65. package/src/app/components/ui/select.tsx +195 -0
  66. package/src/app/components/ui/separator.tsx +26 -0
  67. package/src/app/components/ui/slider.tsx +68 -0
  68. package/src/app/components/ui/sonner.tsx +48 -0
  69. package/src/app/components/ui/tabs.tsx +79 -0
  70. package/src/app/components/ui/textarea.tsx +22 -0
  71. package/src/app/components/ui/toggle-group.tsx +84 -0
  72. package/src/app/components/ui/toggle.tsx +45 -0
  73. package/src/app/components/ui/tooltip.tsx +75 -0
  74. package/src/app/favicon.ico +0 -0
  75. package/src/app/frame/inspect.ts +140 -0
  76. package/src/app/frame/main.tsx +91 -0
  77. package/src/app/frame.html +12 -0
  78. package/src/app/index.html +13 -0
  79. package/src/app/lib/asset-filter.test.ts +176 -0
  80. package/src/app/lib/asset-filter.ts +80 -0
  81. package/src/app/lib/assets.ts +257 -0
  82. package/src/app/lib/design.ts +58 -0
  83. package/src/app/lib/folders.ts +239 -0
  84. package/src/app/lib/frame.ts +38 -0
  85. package/src/app/lib/locale-store.ts +67 -0
  86. package/src/app/lib/page-thumb.tsx +46 -0
  87. package/src/app/lib/pages.ts +28 -0
  88. package/src/app/lib/sdk.ts +43 -0
  89. package/src/app/lib/themes.ts +22 -0
  90. package/src/app/lib/use-locale.ts +8 -0
  91. package/src/app/lib/use-page-module.ts +48 -0
  92. package/src/app/lib/use-page-titles.ts +29 -0
  93. package/src/app/lib/use-restart-server.ts +78 -0
  94. package/src/app/lib/utils.test.ts +25 -0
  95. package/src/app/lib/utils.ts +6 -0
  96. package/src/app/main.tsx +14 -0
  97. package/src/app/routes/assets.tsx +9 -0
  98. package/src/app/routes/home-shell.tsx +252 -0
  99. package/src/app/routes/home.tsx +846 -0
  100. package/src/app/routes/page.tsx +329 -0
  101. package/src/app/routes/themes.tsx +34 -0
  102. package/src/app/styles.css +405 -0
  103. package/src/app/virtual.d.ts +53 -0
  104. package/src/locale/en.ts +218 -0
  105. package/src/locale/format.ts +12 -0
  106. package/src/locale/index.ts +6 -0
  107. package/src/locale/ja.ts +221 -0
  108. package/src/locale/types.ts +215 -0
  109. package/src/locale/zh-cn.ts +218 -0
  110. package/src/locale/zh-tw.ts +218 -0
@@ -0,0 +1,269 @@
1
+ import { MoreHorizontal, Pencil, Trash2 } from 'lucide-react';
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from '@/components/ui/dropdown-menu';
9
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
10
+ import type { Folder, FolderIcon } from '@/lib/sdk';
11
+ import { useLocale } from '@/lib/use-locale';
12
+ import { cn } from '@/lib/utils';
13
+ import { IconPicker } from './icon-picker';
14
+
15
+ export const DOC_DND_MIME = 'application/x-page-id';
16
+
17
+ function useDocDragActive() {
18
+ const [active, setActive] = useState(false);
19
+ useEffect(() => {
20
+ const onStart = (e: DragEvent) => {
21
+ if (e.dataTransfer?.types?.includes(DOC_DND_MIME)) setActive(true);
22
+ };
23
+ const onEnd = () => setActive(false);
24
+ document.addEventListener('dragstart', onStart);
25
+ document.addEventListener('dragend', onEnd);
26
+ document.addEventListener('drop', onEnd);
27
+ return () => {
28
+ document.removeEventListener('dragstart', onStart);
29
+ document.removeEventListener('dragend', onEnd);
30
+ document.removeEventListener('drop', onEnd);
31
+ };
32
+ }, []);
33
+ return active;
34
+ }
35
+
36
+ export function FolderIconChip({ icon, className }: { icon: FolderIcon; className?: string }) {
37
+ if (icon.type === 'emoji') {
38
+ return (
39
+ <span
40
+ className={cn(
41
+ 'inline-flex size-5 items-center justify-center text-[15px] leading-none',
42
+ className,
43
+ )}
44
+ >
45
+ {icon.value}
46
+ </span>
47
+ );
48
+ }
49
+ return (
50
+ <span
51
+ className={cn(
52
+ 'inline-block size-3 rounded-[3px] ring-1 ring-foreground/15 shadow-[inset_0_1px_0_oklch(1_0_0/0.18)]',
53
+ className,
54
+ )}
55
+ style={{ background: icon.value }}
56
+ />
57
+ );
58
+ }
59
+
60
+ type Row =
61
+ | {
62
+ kind: 'folder';
63
+ folder: Folder;
64
+ onRename: (name: string) => void;
65
+ onChangeIcon: (icon: FolderIcon) => void;
66
+ onDelete: () => void;
67
+ }
68
+ | {
69
+ kind: 'all';
70
+ }
71
+ | {
72
+ kind: 'draft';
73
+ }
74
+ | {
75
+ kind: 'themes';
76
+ }
77
+ | {
78
+ kind: 'assets';
79
+ };
80
+
81
+ export function FolderItem({
82
+ row,
83
+ count,
84
+ selected,
85
+ onSelect,
86
+ onDropDoc,
87
+ }: {
88
+ row: Row;
89
+ count: number;
90
+ selected: boolean;
91
+ onSelect: () => void;
92
+ onDropDoc: (pageId: string) => void;
93
+ }) {
94
+ const [renaming, setRenaming] = useState(false);
95
+ const [dragOver, setDragOver] = useState(false);
96
+ const dragDepth = useRef(0);
97
+ const [draftName, setDraftName] = useState(row.kind === 'folder' ? row.folder.name : '');
98
+ const docDragActive = useDocDragActive();
99
+ const t = useLocale();
100
+
101
+ const acceptsDocDrop = row.kind === 'draft' || row.kind === 'folder';
102
+ const isDocDrag = (e: React.DragEvent) =>
103
+ acceptsDocDrop && e.dataTransfer.types.includes(DOC_DND_MIME);
104
+ const handleDragEnter = (e: React.DragEvent) => {
105
+ if (!isDocDrag(e)) return;
106
+ dragDepth.current += 1;
107
+ if (dragDepth.current === 1) setDragOver(true);
108
+ };
109
+ const handleDragOver = (e: React.DragEvent) => {
110
+ if (!isDocDrag(e)) return;
111
+ e.preventDefault();
112
+ e.dataTransfer.dropEffect = 'move';
113
+ };
114
+ const handleDragLeave = (e: React.DragEvent) => {
115
+ if (!isDocDrag(e)) return;
116
+ dragDepth.current = Math.max(0, dragDepth.current - 1);
117
+ if (dragDepth.current === 0) setDragOver(false);
118
+ };
119
+ const handleDrop = (e: React.DragEvent) => {
120
+ if (!acceptsDocDrop) return;
121
+ const pageId = e.dataTransfer.getData(DOC_DND_MIME);
122
+ dragDepth.current = 0;
123
+ setDragOver(false);
124
+ if (!pageId) return;
125
+ e.preventDefault();
126
+ onDropDoc(pageId);
127
+ };
128
+
129
+ const icon: FolderIcon =
130
+ row.kind === 'all'
131
+ ? { type: 'emoji', value: '🎞️' }
132
+ : row.kind === 'draft'
133
+ ? { type: 'emoji', value: '📝' }
134
+ : row.kind === 'themes'
135
+ ? { type: 'emoji', value: '🎨' }
136
+ : row.kind === 'assets'
137
+ ? { type: 'emoji', value: '🗂️' }
138
+ : row.folder.icon;
139
+ const label =
140
+ row.kind === 'all'
141
+ ? t.home.pages
142
+ : row.kind === 'draft'
143
+ ? t.home.draft
144
+ : row.kind === 'themes'
145
+ ? t.home.themes
146
+ : row.kind === 'assets'
147
+ ? t.home.assets
148
+ : row.folder.name;
149
+
150
+ const commitRename = () => {
151
+ if (row.kind !== 'folder') return;
152
+ const trimmed = draftName.trim();
153
+ if (trimmed && trimmed !== row.folder.name) row.onRename(trimmed);
154
+ setRenaming(false);
155
+ };
156
+
157
+ return (
158
+ // biome-ignore lint/a11y/noStaticElementInteractions: drag-and-drop target wraps interactive children
159
+ <div
160
+ className={cn(
161
+ 'group relative flex items-center gap-2.5 rounded-[5px] px-2 py-[5px] text-[12.5px] transition-colors',
162
+ selected
163
+ ? 'bg-muted text-foreground before:absolute before:inset-y-1.5 before:-left-0.5 before:w-[2px] before:rounded-full before:bg-brand'
164
+ : 'text-foreground/70 hover:bg-muted/60 hover:text-foreground',
165
+ docDragActive && acceptsDocDrop && !dragOver && 'ring-1 ring-foreground/10',
166
+ dragOver &&
167
+ 'bg-brand/10 text-foreground ring-1 ring-brand ring-offset-1 ring-offset-sidebar motion-safe:scale-[1.01] motion-safe:transition-transform',
168
+ )}
169
+ onDragEnter={handleDragEnter}
170
+ onDragOver={handleDragOver}
171
+ onDragLeave={handleDragLeave}
172
+ onDrop={handleDrop}
173
+ >
174
+ {row.kind === 'folder' && import.meta.env.DEV ? (
175
+ <Popover>
176
+ <PopoverTrigger
177
+ render={
178
+ <button
179
+ type="button"
180
+ className="flex size-5 shrink-0 items-center justify-center rounded transition-transform hover:scale-110"
181
+ aria-label={t.home.changeIcon}
182
+ onClick={(e) => e.stopPropagation()}
183
+ >
184
+ <FolderIconChip icon={icon} />
185
+ </button>
186
+ }
187
+ />
188
+ <PopoverContent side="right" align="start" className="w-auto p-2">
189
+ <IconPicker value={row.folder.icon} onChange={(next) => row.onChangeIcon(next)} />
190
+ </PopoverContent>
191
+ </Popover>
192
+ ) : (
193
+ <button
194
+ type="button"
195
+ onClick={onSelect}
196
+ aria-label={label}
197
+ className="flex size-5 shrink-0 items-center justify-center"
198
+ >
199
+ <FolderIconChip icon={icon} />
200
+ </button>
201
+ )}
202
+
203
+ {renaming && row.kind === 'folder' ? (
204
+ <input
205
+ value={draftName}
206
+ onChange={(e) => setDraftName(e.target.value)}
207
+ onBlur={commitRename}
208
+ onKeyDown={(e) => {
209
+ if (e.nativeEvent.isComposing) return;
210
+ if (e.key === 'Enter') commitRename();
211
+ if (e.key === 'Escape') {
212
+ setDraftName(row.folder.name);
213
+ setRenaming(false);
214
+ }
215
+ }}
216
+ maxLength={40}
217
+ className="min-w-0 flex-1 rounded-[3px] bg-card px-1 text-[12.5px] outline-none ring-1 ring-foreground/20"
218
+ />
219
+ ) : (
220
+ <button type="button" onClick={onSelect} className="min-w-0 flex-1 truncate text-left">
221
+ {label}
222
+ </button>
223
+ )}
224
+
225
+ <span
226
+ className={cn(
227
+ 'folio ml-auto shrink-0 transition-opacity',
228
+ row.kind === 'folder' &&
229
+ import.meta.env.DEV &&
230
+ 'group-hover:opacity-0 group-has-[[aria-expanded=true]]:opacity-0',
231
+ )}
232
+ >
233
+ {count.toString().padStart(2, '0')}
234
+ </span>
235
+
236
+ {row.kind === 'folder' && import.meta.env.DEV && (
237
+ <DropdownMenu>
238
+ <DropdownMenuTrigger
239
+ render={
240
+ <button
241
+ type="button"
242
+ 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"
244
+ aria-label={t.home.folderActions}
245
+ >
246
+ <MoreHorizontal className="mx-auto size-3.5" />
247
+ </button>
248
+ }
249
+ />
250
+ <DropdownMenuContent align="end" className="min-w-[140px]">
251
+ <DropdownMenuItem
252
+ onClick={() => {
253
+ setDraftName(row.folder.name);
254
+ setRenaming(true);
255
+ }}
256
+ >
257
+ <Pencil />
258
+ {t.common.rename}
259
+ </DropdownMenuItem>
260
+ <DropdownMenuItem variant="destructive" onClick={() => row.onDelete()}>
261
+ <Trash2 />
262
+ {t.common.delete}
263
+ </DropdownMenuItem>
264
+ </DropdownMenuContent>
265
+ </DropdownMenu>
266
+ )}
267
+ </div>
268
+ );
269
+ }
@@ -0,0 +1,61 @@
1
+ import EmojiPicker, { EmojiStyle, Theme } from 'emoji-picker-react';
2
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
3
+ import type { FolderIcon } from '@/lib/sdk';
4
+ import { useLocale } from '@/lib/use-locale';
5
+
6
+ export const PRESET_COLORS = [
7
+ '#e5484d', // red
8
+ '#f76b15', // orange
9
+ '#ffb224', // amber
10
+ '#30a46c', // green
11
+ '#12a594', // teal
12
+ '#0091ff', // blue
13
+ '#6e56cf', // violet
14
+ '#60646c', // gray
15
+ ];
16
+
17
+ export function IconPicker({
18
+ value,
19
+ onChange,
20
+ }: {
21
+ value: FolderIcon;
22
+ onChange: (icon: FolderIcon) => void;
23
+ }) {
24
+ const t = useLocale();
25
+ return (
26
+ <Tabs defaultValue={value.type} className="w-[320px]">
27
+ <TabsList className="w-full">
28
+ <TabsTrigger value="emoji">{t.home.iconEmojiTab}</TabsTrigger>
29
+ <TabsTrigger value="color">{t.home.iconColorTab}</TabsTrigger>
30
+ </TabsList>
31
+
32
+ <TabsContent value="emoji">
33
+ <EmojiPicker
34
+ lazyLoadEmojis
35
+ emojiStyle={EmojiStyle.NATIVE}
36
+ theme={Theme.AUTO}
37
+ width="100%"
38
+ height={360}
39
+ onEmojiClick={(data) => onChange({ type: 'emoji', value: data.emoji })}
40
+ previewConfig={{ showPreview: false }}
41
+ skinTonesDisabled
42
+ />
43
+ </TabsContent>
44
+
45
+ <TabsContent value="color">
46
+ <div className="grid grid-cols-8 gap-1.5 py-2">
47
+ {PRESET_COLORS.map((c) => (
48
+ <button
49
+ key={c}
50
+ type="button"
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"
53
+ style={{ background: c }}
54
+ aria-label={c}
55
+ />
56
+ ))}
57
+ </div>
58
+ </TabsContent>
59
+ </Tabs>
60
+ );
61
+ }
@@ -0,0 +1,139 @@
1
+ import config from 'virtual:open-pages/config';
2
+ import { Loader2, RefreshCw, RotateCw } from 'lucide-react';
3
+ import { useEffect, useState } from 'react';
4
+ import { toast } from 'sonner';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
7
+ import { format, useLocale } from '@/lib/use-locale';
8
+ import { useRestartServer } from '@/lib/use-restart-server';
9
+
10
+ type UpdateCheck = { current: string; latest: string | null; outdated: boolean };
11
+ type UpdateStatus = 'idle' | 'running' | 'done' | 'error';
12
+
13
+ const buttonClassName =
14
+ 'h-6 w-fit rounded-[5px] border border-background/15 bg-background/8 px-2 text-[11px] text-background shadow-none hover:bg-background/14';
15
+
16
+ export function SidebarFooter() {
17
+ const t = useLocale();
18
+ const [update, setUpdate] = useState<UpdateCheck | null>(null);
19
+ const [updateStatus, setUpdateStatus] = useState<UpdateStatus>('idle');
20
+ const { canRestart, restarting, restartServer } = useRestartServer();
21
+ const [open, setOpen] = useState(false);
22
+
23
+ useEffect(() => {
24
+ if (!import.meta.env.DEV) return;
25
+ let cancelled = false;
26
+ fetch('/__update-check')
27
+ .then((res) => (res.ok ? (res.json() as Promise<UpdateCheck>) : null))
28
+ .then((data) => {
29
+ if (!cancelled && data?.outdated) setUpdate(data);
30
+ })
31
+ .catch(() => {});
32
+ return () => {
33
+ cancelled = true;
34
+ };
35
+ }, []);
36
+
37
+ const label = `v${config.version}`;
38
+ const isUpdating = updateStatus === 'running';
39
+ const keepOpen = updateStatus === 'running' || restarting;
40
+
41
+ async function updatePackage() {
42
+ if (isUpdating) return;
43
+ setUpdateStatus('running');
44
+ setOpen(true);
45
+ try {
46
+ const res = await fetch('/__update-package', { method: 'POST' });
47
+ if (!res.ok) throw new Error('update failed');
48
+ setUpdateStatus('done');
49
+ toast.success(t.home.updatePackageDone);
50
+ } catch {
51
+ setUpdateStatus('error');
52
+ toast.error(t.home.updatePackageFailed);
53
+ }
54
+ }
55
+
56
+ const versionRow = (
57
+ <span className="inline-flex cursor-default items-center gap-1.5">
58
+ {update?.latest && <span className="size-1.5 rounded-full bg-brand" aria-hidden />}
59
+ {label}
60
+ </span>
61
+ );
62
+
63
+ return (
64
+ <div className="px-4 py-3 text-[11px] text-muted-foreground/70 tabular-nums">
65
+ {update?.latest ? (
66
+ <TooltipProvider delay={200}>
67
+ <Tooltip
68
+ open={open}
69
+ onOpenChange={(next) => {
70
+ if (!next && keepOpen) return;
71
+ setOpen(next);
72
+ }}
73
+ >
74
+ <TooltipTrigger render={versionRow} />
75
+ <TooltipContent
76
+ side="top"
77
+ align="start"
78
+ alignOffset={-8}
79
+ sideOffset={9}
80
+ collisionPadding={12}
81
+ className="flex w-[232px] max-w-[calc(100vw-24px)] flex-col gap-2.5 rounded-[8px] border border-background/10 bg-foreground/95 p-2.5 text-[11.5px] leading-4 shadow-[0_12px_32px_oklch(0_0_0/0.28)] backdrop-blur"
82
+ >
83
+ {updateStatus === 'done' ? (
84
+ <>
85
+ <span className="pr-1 text-background/92">{t.home.updatePackageDone}</span>
86
+ {canRestart && (
87
+ <Button
88
+ type="button"
89
+ size="xs"
90
+ variant="secondary"
91
+ className={buttonClassName}
92
+ disabled={restarting}
93
+ onClick={restartServer}
94
+ >
95
+ {restarting ? (
96
+ <Loader2 className="animate-spin" aria-hidden />
97
+ ) : (
98
+ <RotateCw aria-hidden />
99
+ )}
100
+ {restarting ? t.home.restartingServer : t.home.restartServer}
101
+ </Button>
102
+ )}
103
+ </>
104
+ ) : (
105
+ <>
106
+ <span className="pr-1 text-background/92">
107
+ {format(t.home.updateAvailable, { version: update.latest })}
108
+ </span>
109
+ <Button
110
+ type="button"
111
+ size="xs"
112
+ variant="secondary"
113
+ className={buttonClassName}
114
+ disabled={isUpdating}
115
+ onClick={updatePackage}
116
+ >
117
+ {isUpdating ? (
118
+ <Loader2 className="animate-spin" aria-hidden />
119
+ ) : (
120
+ <RefreshCw aria-hidden />
121
+ )}
122
+ {isUpdating ? t.home.updatingPackage : t.home.updatePackage}
123
+ </Button>
124
+ {updateStatus === 'error' && (
125
+ <span className="text-[11px] leading-4 text-background/65">
126
+ {t.home.updatePackageFailed}
127
+ </span>
128
+ )}
129
+ </>
130
+ )}
131
+ </TooltipContent>
132
+ </Tooltip>
133
+ </TooltipProvider>
134
+ ) : (
135
+ versionRow
136
+ )}
137
+ </div>
138
+ );
139
+ }