@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,26 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Separator as SeparatorPrimitive } from '@base-ui/react/separator';
5
+
6
+ import { cn } from '@/lib/utils';
7
+
8
+ function Separator({
9
+ className,
10
+ orientation = 'horizontal',
11
+ ...props
12
+ }: React.ComponentProps<typeof SeparatorPrimitive>) {
13
+ return (
14
+ <SeparatorPrimitive
15
+ data-slot="separator"
16
+ orientation={orientation}
17
+ className={cn(
18
+ 'shrink-0 bg-hairline data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch',
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ export { Separator };
@@ -0,0 +1,68 @@
1
+ import * as React from "react"
2
+ import { Slider as DocrPrimitive } from "@base-ui/react/slider"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Slider({
7
+ className,
8
+ defaultValue,
9
+ value,
10
+ min = 0,
11
+ max = 100,
12
+ ...props
13
+ }: React.ComponentProps<typeof DocrPrimitive.Root>) {
14
+ const _values = React.useMemo(
15
+ () =>
16
+ Array.isArray(value)
17
+ ? value
18
+ : Array.isArray(defaultValue)
19
+ ? defaultValue
20
+ : [min, max],
21
+ [value, defaultValue, min, max]
22
+ )
23
+
24
+ return (
25
+ <DocrPrimitive.Root
26
+ data-slot="slider"
27
+ defaultValue={defaultValue}
28
+ value={value}
29
+ min={min}
30
+ max={max}
31
+ thumbAlignment="edge"
32
+ className={cn(
33
+ "group/slider relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
34
+ className
35
+ )}
36
+ {...props}
37
+ >
38
+ <DocrPrimitive.Control
39
+ data-slot="slider-control"
40
+ className="relative flex w-full grow items-center data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col"
41
+ >
42
+ <DocrPrimitive.Track
43
+ data-slot="slider-track"
44
+ className={cn(
45
+ "relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-[3px] data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-[3px]"
46
+ )}
47
+ >
48
+ <DocrPrimitive.Indicator
49
+ data-slot="slider-range"
50
+ className={cn(
51
+ "absolute bg-foreground data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
52
+ )}
53
+ />
54
+ </DocrPrimitive.Track>
55
+ {Array.from({ length: _values.length }, (_, index) => (
56
+ <DocrPrimitive.Thumb
57
+ data-slot="slider-thumb"
58
+ index={index}
59
+ key={index}
60
+ className="block size-3.5 shrink-0 rounded-full border border-foreground bg-card shadow-edge transition-transform hover:scale-110 focus-visible:scale-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring/50 disabled:pointer-events-none disabled:opacity-50"
61
+ />
62
+ ))}
63
+ </DocrPrimitive.Control>
64
+ </DocrPrimitive.Root>
65
+ )
66
+ }
67
+
68
+ export { Slider }
@@ -0,0 +1,48 @@
1
+ import {
2
+ CircleCheckIcon,
3
+ InfoIcon,
4
+ Loader2Icon,
5
+ OctagonXIcon,
6
+ TriangleAlertIcon,
7
+ } from "lucide-react"
8
+ import { useTheme } from "next-themes"
9
+ import { Toaster as Sonner, type ToasterProps } from "sonner"
10
+
11
+ const Toaster = ({ ...props }: ToasterProps) => {
12
+ const { theme = "system" } = useTheme()
13
+
14
+ return (
15
+ <Sonner
16
+ theme={theme as ToasterProps["theme"]}
17
+ className="toaster group"
18
+ icons={{
19
+ success: <CircleCheckIcon className="size-4" />,
20
+ info: <InfoIcon className="size-4" />,
21
+ warning: <TriangleAlertIcon className="size-4" />,
22
+ error: <OctagonXIcon className="size-4" />,
23
+ loading: <Loader2Icon className="size-4 animate-spin" />,
24
+ }}
25
+ style={
26
+ {
27
+ "--normal-bg": "var(--popover)",
28
+ "--normal-text": "var(--popover-foreground)",
29
+ "--normal-border": "var(--border)",
30
+ "--border-radius": "8px",
31
+ "--font-family":
32
+ "Geist Variable, -apple-system, BlinkMacSystemFont, sans-serif",
33
+ } as React.CSSProperties
34
+ }
35
+ toastOptions={{
36
+ classNames: {
37
+ toast:
38
+ "!font-sans !text-[12.5px] !shadow-floating !border-border !rounded-[8px]",
39
+ title: "!font-medium !text-[12.5px] !tracking-tight",
40
+ description: "!text-[12px] !text-muted-foreground",
41
+ },
42
+ }}
43
+ {...props}
44
+ />
45
+ )
46
+ }
47
+
48
+ export { Toaster }
@@ -0,0 +1,79 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { Tabs as TabsPrimitive } from '@base-ui/react/tabs';
4
+
5
+ import { cn } from '@/lib/utils';
6
+
7
+ function Tabs({
8
+ className,
9
+ orientation = 'horizontal',
10
+ ...props
11
+ }: React.ComponentProps<typeof TabsPrimitive.Root>) {
12
+ return (
13
+ <TabsPrimitive.Root
14
+ data-slot="tabs"
15
+ data-orientation={orientation}
16
+ orientation={orientation}
17
+ className={cn('group/tabs flex gap-2 data-[orientation=horizontal]:flex-col', className)}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ const tabsListVariants = cva(
24
+ 'group/tabs-list inline-flex w-fit items-center justify-center rounded-[6px] p-[2px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-7 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none',
25
+ {
26
+ variants: {
27
+ variant: {
28
+ default: 'bg-muted/70 ring-1 ring-inset ring-border/60',
29
+ line: 'gap-1 bg-transparent',
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: 'default',
34
+ },
35
+ },
36
+ );
37
+
38
+ function TabsList({
39
+ className,
40
+ variant = 'default',
41
+ ...props
42
+ }: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>) {
43
+ return (
44
+ <TabsPrimitive.List
45
+ data-slot="tabs-list"
46
+ data-variant={variant}
47
+ className={cn(tabsListVariants({ variant }), className)}
48
+ {...props}
49
+ />
50
+ );
51
+ }
52
+
53
+ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Tab>) {
54
+ return (
55
+ <TabsPrimitive.Tab
56
+ data-slot="tabs-trigger"
57
+ className={cn(
58
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-[5px] border border-transparent px-2.5 text-[12px] font-medium whitespace-nowrap text-foreground/55 transition-colors group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
59
+ 'group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent',
60
+ 'data-active:bg-card data-active:text-foreground data-active:shadow-edge dark:data-active:bg-foreground/10',
61
+ 'after:absolute after:bg-brand after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:-bottom-[6px] group-data-[orientation=horizontal]/tabs:after:h-[2px] group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100',
62
+ className,
63
+ )}
64
+ {...props}
65
+ />
66
+ );
67
+ }
68
+
69
+ function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Panel>) {
70
+ return (
71
+ <TabsPrimitive.Panel
72
+ data-slot="tabs-content"
73
+ className={cn('flex-1 outline-none', className)}
74
+ {...props}
75
+ />
76
+ );
77
+ }
78
+
79
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/lib/utils';
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ 'flex field-sizing-content min-h-16 w-full rounded-[6px] border border-border bg-background px-2.5 py-2 text-[13px] leading-relaxed outline-none',
11
+ 'transition-colors placeholder:text-muted-foreground/70',
12
+ 'focus-visible:border-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/30',
13
+ 'disabled:cursor-not-allowed disabled:opacity-50',
14
+ 'aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/25',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ }
21
+
22
+ export { Textarea };
@@ -0,0 +1,84 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { type VariantProps } from "class-variance-authority"
5
+ import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
6
+ import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
7
+
8
+ import { cn } from "@/lib/utils"
9
+ import { toggleVariants } from "@/components/ui/toggle"
10
+
11
+ const ToggleGroupContext = React.createContext<
12
+ VariantProps<typeof toggleVariants> & {
13
+ spacing?: number
14
+ }
15
+ >({
16
+ size: "default",
17
+ variant: "default",
18
+ spacing: 0,
19
+ })
20
+
21
+ function ToggleGroup({
22
+ className,
23
+ variant,
24
+ size,
25
+ spacing = 0,
26
+ children,
27
+ ...props
28
+ }: React.ComponentProps<typeof ToggleGroupPrimitive> &
29
+ VariantProps<typeof toggleVariants> & {
30
+ spacing?: number
31
+ }) {
32
+ return (
33
+ <ToggleGroupPrimitive
34
+ data-slot="toggle-group"
35
+ data-variant={variant}
36
+ data-size={size}
37
+ data-spacing={spacing}
38
+ style={{ "--gap": spacing } as React.CSSProperties}
39
+ className={cn(
40
+ "group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-[5px]",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ <ToggleGroupContext.Provider value={{ variant, size, spacing }}>
46
+ {children}
47
+ </ToggleGroupContext.Provider>
48
+ </ToggleGroupPrimitive>
49
+ )
50
+ }
51
+
52
+ function ToggleGroupItem({
53
+ className,
54
+ children,
55
+ variant,
56
+ size,
57
+ ...props
58
+ }: React.ComponentProps<typeof TogglePrimitive> &
59
+ VariantProps<typeof toggleVariants>) {
60
+ const context = React.useContext(ToggleGroupContext)
61
+
62
+ return (
63
+ <TogglePrimitive
64
+ data-slot="toggle-group-item"
65
+ data-variant={context.variant || variant}
66
+ data-size={context.size || size}
67
+ data-spacing={context.spacing}
68
+ className={cn(
69
+ toggleVariants({
70
+ variant: context.variant || variant,
71
+ size: context.size || size,
72
+ }),
73
+ "w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
74
+ "data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-[5px] data-[spacing=0]:last:rounded-r-[5px] data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
75
+ className
76
+ )}
77
+ {...props}
78
+ >
79
+ {children}
80
+ </TogglePrimitive>
81
+ )
82
+ }
83
+
84
+ export { ToggleGroup, ToggleGroupItem }
@@ -0,0 +1,45 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const toggleVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 rounded-[5px] text-[12px] font-medium whitespace-nowrap outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50 data-pressed:bg-foreground data-pressed:text-background [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-transparent text-foreground/70",
13
+ outline:
14
+ "border border-border bg-card text-foreground/75 hover:border-foreground/20 data-pressed:border-foreground",
15
+ },
16
+ size: {
17
+ default: "h-8 min-w-8 px-2",
18
+ sm: "h-7 min-w-7 px-1.5",
19
+ lg: "h-9 min-w-9 px-2.5",
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ variant: "default",
24
+ size: "default",
25
+ },
26
+ }
27
+ )
28
+
29
+ function Toggle({
30
+ className,
31
+ variant,
32
+ size,
33
+ ...props
34
+ }: React.ComponentProps<typeof TogglePrimitive> &
35
+ VariantProps<typeof toggleVariants>) {
36
+ return (
37
+ <TogglePrimitive
38
+ data-slot="toggle"
39
+ className={cn(toggleVariants({ variant, size, className }))}
40
+ {...props}
41
+ />
42
+ )
43
+ }
44
+
45
+ export { Toggle, toggleVariants }
@@ -0,0 +1,75 @@
1
+ import * as React from "react"
2
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function TooltipProvider({
7
+ delay = 0,
8
+ ...props
9
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
10
+ return <TooltipPrimitive.Provider delay={delay} {...props} />
11
+ }
12
+
13
+ function Tooltip({
14
+ ...props
15
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
16
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
17
+ }
18
+
19
+ function TooltipTrigger({
20
+ ...props
21
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
22
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
23
+ }
24
+
25
+ function TooltipContent({
26
+ className,
27
+ side,
28
+ sideOffset = 0,
29
+ align,
30
+ alignOffset,
31
+ collisionPadding,
32
+ children,
33
+ container,
34
+ ...props
35
+ }: React.ComponentProps<typeof TooltipPrimitive.Popup> &
36
+ Pick<
37
+ React.ComponentProps<typeof TooltipPrimitive.Positioner>,
38
+ "side" | "sideOffset" | "align" | "alignOffset" | "collisionPadding"
39
+ > & {
40
+ container?: React.ComponentProps<typeof TooltipPrimitive.Portal>["container"]
41
+ }) {
42
+ return (
43
+ <TooltipPrimitive.Portal container={container}>
44
+ <TooltipPrimitive.Positioner
45
+ side={side}
46
+ sideOffset={sideOffset}
47
+ align={align}
48
+ alignOffset={alignOffset}
49
+ collisionPadding={collisionPadding}
50
+ className="isolate z-50"
51
+ >
52
+ <TooltipPrimitive.Popup
53
+ data-slot="tooltip-content"
54
+ className={cn(
55
+ "w-fit origin-(--transform-origin) rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background transition-[transform,scale,opacity] data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0 data-[instant]:duration-0",
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ {children}
61
+ <TooltipPrimitive.Arrow
62
+ className={cn(
63
+ "size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-inherit",
64
+ "data-[side=top]:-bottom-2.5 data-[side=bottom]:top-1",
65
+ "data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2",
66
+ "data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2",
67
+ )}
68
+ />
69
+ </TooltipPrimitive.Popup>
70
+ </TooltipPrimitive.Positioner>
71
+ </TooltipPrimitive.Portal>
72
+ )
73
+ }
74
+
75
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
Binary file
@@ -0,0 +1,140 @@
1
+ // Click-to-source inside the page frame. Every host element in page source
2
+ // carries `data-op-loc="<line>:<col>"` (loc-tags plugin, dev only); hovering
3
+ // outlines the innermost tagged element and a click reports it to the
4
+ // workspace window instead of reaching the page.
5
+
6
+ import type { FrameMessage } from '../lib/frame';
7
+
8
+ const LOC_ATTR = 'data-op-loc';
9
+ const TEXT_MAX = 120;
10
+
11
+ type Post = (msg: FrameMessage) => void;
12
+
13
+ function box(color: string, fill: string): HTMLDivElement {
14
+ const el = document.createElement('div');
15
+ el.style.cssText = [
16
+ 'position:fixed',
17
+ 'pointer-events:none',
18
+ 'z-index:2147483647',
19
+ 'border-radius:2px',
20
+ `border:1.5px solid ${color}`,
21
+ `background:${fill}`,
22
+ 'display:none',
23
+ 'box-sizing:border-box',
24
+ ].join(';');
25
+ return el;
26
+ }
27
+
28
+ export function installInspector(post: Post) {
29
+ const hover = box('rgba(59,130,246,0.85)', 'rgba(59,130,246,0.08)');
30
+ const selected = box('rgb(37,99,235)', 'rgba(37,99,235,0.14)');
31
+ document.documentElement.append(hover, selected);
32
+
33
+ let on = false;
34
+ let hoverEl: Element | null = null;
35
+ let selectedLoc: string | null = null;
36
+ let raf = 0;
37
+
38
+ const place = (el: Element | null, target: HTMLDivElement) => {
39
+ if (!el || !el.isConnected) {
40
+ target.style.display = 'none';
41
+ return;
42
+ }
43
+ const r = el.getBoundingClientRect();
44
+ target.style.display = 'block';
45
+ target.style.left = `${r.left - 2}px`;
46
+ target.style.top = `${r.top - 2}px`;
47
+ target.style.width = `${r.width + 4}px`;
48
+ target.style.height = `${r.height + 4}px`;
49
+ };
50
+
51
+ const findSelected = (): Element | null =>
52
+ selectedLoc ? document.querySelector(`[${LOC_ATTR}="${CSS.escape(selectedLoc)}"]`) : null;
53
+
54
+ const sync = () => {
55
+ raf = 0;
56
+ place(on ? hoverEl : null, hover);
57
+ place(findSelected(), selected);
58
+ };
59
+ const schedule = () => {
60
+ if (!raf) raf = requestAnimationFrame(sync);
61
+ };
62
+
63
+ const targetOf = (e: Event): Element | null => {
64
+ const t = e.target;
65
+ if (!(t instanceof Element)) return null;
66
+ return t.closest(`[${LOC_ATTR}]`);
67
+ };
68
+
69
+ const onMove = (e: MouseEvent) => {
70
+ if (!on) return;
71
+ const next = targetOf(e);
72
+ if (next !== hoverEl) {
73
+ hoverEl = next;
74
+ schedule();
75
+ }
76
+ };
77
+ const onLeave = () => {
78
+ hoverEl = null;
79
+ schedule();
80
+ };
81
+ const onClick = (e: MouseEvent) => {
82
+ if (!on) return;
83
+ e.preventDefault();
84
+ e.stopPropagation();
85
+ const el = targetOf(e);
86
+ if (!el) return;
87
+ const loc = el.getAttribute(LOC_ATTR) ?? '';
88
+ selectedLoc = loc;
89
+ schedule();
90
+ post({
91
+ type: 'op:select',
92
+ loc,
93
+ tag: el.tagName.toLowerCase(),
94
+ text: (el.textContent ?? '').replace(/\s+/g, ' ').trim().slice(0, TEXT_MAX),
95
+ });
96
+ };
97
+ const swallow = (e: Event) => {
98
+ if (on) {
99
+ e.preventDefault();
100
+ e.stopPropagation();
101
+ }
102
+ };
103
+ const onKey = (e: KeyboardEvent) => {
104
+ const tag = (e.target as HTMLElement | null)?.tagName;
105
+ if (
106
+ tag === 'INPUT' ||
107
+ tag === 'TEXTAREA' ||
108
+ (e.target as HTMLElement | null)?.isContentEditable
109
+ )
110
+ return;
111
+ if (e.key === 'i' || e.key === 'Escape') post({ type: 'op:key', key: e.key });
112
+ };
113
+
114
+ document.addEventListener('mousemove', onMove, true);
115
+ document.addEventListener('mouseleave', onLeave, true);
116
+ document.addEventListener('click', onClick, true);
117
+ document.addEventListener('mousedown', swallow, true);
118
+ document.addEventListener('pointerdown', swallow, true);
119
+ document.addEventListener('keydown', onKey, true);
120
+ window.addEventListener('scroll', schedule, true);
121
+ window.addEventListener('resize', schedule);
122
+ new MutationObserver(schedule).observe(document.documentElement, {
123
+ subtree: true,
124
+ childList: true,
125
+ attributes: true,
126
+ });
127
+
128
+ return {
129
+ setInspecting(next: boolean) {
130
+ on = next;
131
+ document.documentElement.style.cursor = on ? 'crosshair' : '';
132
+ if (!on) hoverEl = null;
133
+ schedule();
134
+ },
135
+ setSelected(loc: string | null) {
136
+ selectedLoc = loc;
137
+ schedule();
138
+ },
139
+ };
140
+ }
@@ -0,0 +1,91 @@
1
+ import 'virtual:open-pages/pages.css';
2
+ import { loadPage } from 'virtual:open-pages/pages';
3
+ import { loadThemeDemo } from 'virtual:open-pages/themes';
4
+ import { createElement, StrictMode } from 'react';
5
+ import { createRoot } from 'react-dom/client';
6
+ import type { FrameMessage, WorkspaceMessage } from '../lib/frame';
7
+ import { pageChangeIncludes } from '../lib/pages';
8
+ import type { PageModule } from '../lib/sdk';
9
+ import { installInspector } from './inspect';
10
+
11
+ const params = new URLSearchParams(window.location.search);
12
+ const pageId = params.get('page');
13
+ const themeId = params.get('theme');
14
+
15
+ const embedded = window.parent !== window;
16
+ const post = (msg: FrameMessage) => {
17
+ if (embedded) window.parent.postMessage(msg, '*');
18
+ };
19
+
20
+ // biome-ignore lint/style/noNonNullAssertion: #root is guaranteed by frame.html
21
+ const root = createRoot(document.getElementById('root')!);
22
+
23
+ function renderError(message: string) {
24
+ root.render(
25
+ createElement(
26
+ 'pre',
27
+ {
28
+ style: {
29
+ margin: 0,
30
+ padding: '16px 20px',
31
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
32
+ fontSize: 12,
33
+ lineHeight: 1.5,
34
+ color: '#b91c1c',
35
+ background: '#fef2f2',
36
+ whiteSpace: 'pre-wrap',
37
+ },
38
+ },
39
+ message,
40
+ ),
41
+ );
42
+ post({ type: 'op:error', message });
43
+ }
44
+
45
+ async function load(): Promise<PageModule> {
46
+ if (themeId) return loadThemeDemo(themeId);
47
+ if (pageId) return loadPage(pageId);
48
+ throw new Error('frame.html needs ?page=<id> or ?theme=<id>');
49
+ }
50
+
51
+ let seq = 0;
52
+ async function mount() {
53
+ const my = ++seq;
54
+ try {
55
+ const mod = await load();
56
+ if (my !== seq) return;
57
+ if (typeof mod.default !== 'function') {
58
+ throw new Error(
59
+ `${themeId ? `themes/${themeId}.demo.tsx` : `pages/${pageId}/index.tsx`} must default-export a component. Got: ${typeof mod.default}`,
60
+ );
61
+ }
62
+ const title = mod.meta?.title ?? pageId ?? themeId ?? '';
63
+ document.title = title;
64
+ root.render(createElement(StrictMode, null, createElement(mod.default)));
65
+ post({ type: 'op:ready', title });
66
+ } catch (e) {
67
+ if (my !== seq) return;
68
+ renderError(e instanceof Error ? (e.stack ?? e.message) : String(e));
69
+ }
70
+ }
71
+
72
+ void mount();
73
+
74
+ if (import.meta.hot && pageId) {
75
+ import.meta.hot.on('open-pages:page-changed', (data: unknown) => {
76
+ if (!pageChangeIncludes(data, pageId)) return;
77
+ // The virtual module refreshes its cache-bust token on this same event.
78
+ queueMicrotask(() => void mount());
79
+ });
80
+ }
81
+
82
+ if (embedded && import.meta.env.DEV) {
83
+ const inspector = installInspector(post);
84
+ window.addEventListener('message', (event: MessageEvent<WorkspaceMessage>) => {
85
+ if (event.source !== window.parent) return;
86
+ const msg = event.data;
87
+ if (!msg || typeof msg !== 'object') return;
88
+ if (msg.type === 'op:inspect') inspector.setInspecting(msg.on);
89
+ else if (msg.type === 'op:select') inspector.setSelected(msg.loc);
90
+ });
91
+ }