create-widget 24.1.1-beta.8 → 26.7.21
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/LICENSE +27 -27
- package/bin/index.js +277 -0
- package/eslint/eslint.config.js +12 -0
- package/package.json +24 -15
- package/readme.md +82 -0
- package/template/react/README.md +41 -0
- package/template/react/components.json +25 -0
- package/template/react/env.d.ts +1 -0
- package/template/react/index.html +13 -0
- package/template/react/package.json +42 -0
- package/template/react/public/apple-touch-icon.png +0 -0
- package/template/react/public/favicon-96x96.png +0 -0
- package/template/react/public/favicon.ico +0 -0
- package/template/react/public/favicon.svg +3 -0
- package/template/react/public/logo.png +0 -0
- package/template/react/public/site.webmanifest +21 -0
- package/template/react/public/web-app-manifest-192x192.png +0 -0
- package/template/react/public/web-app-manifest-512x512.png +0 -0
- package/template/{public → react/public}/widget.json +55 -50
- package/template/react/src/App.tsx +17 -0
- package/template/react/src/assets/main.css +221 -0
- package/template/react/src/components/ui/button.tsx +67 -0
- package/template/react/src/components/ui/card.tsx +103 -0
- package/template/react/src/components/ui/color-picker.tsx +171 -0
- package/template/react/src/components/ui/field.tsx +238 -0
- package/template/react/src/components/ui/input.tsx +19 -0
- package/template/react/src/components/ui/label.tsx +22 -0
- package/template/react/src/components/ui/separator.tsx +28 -0
- package/template/react/src/components/ui/slider.tsx +57 -0
- package/template/react/src/components/ui/switch.tsx +31 -0
- package/template/react/src/components/ui/tabs.tsx +88 -0
- package/template/react/src/lib/utils.ts +7 -0
- package/template/react/src/main.tsx +20 -0
- package/template/react/src/pages/home-page.tsx +66 -0
- package/template/react/src/widgets/clock/Clock.widget.ts +21 -0
- package/template/react/src/widgets/clock/ClockConfigView.tsx +112 -0
- package/template/react/src/widgets/clock/ClockWidgetRoutes.ts +25 -0
- package/template/react/src/widgets/clock/ClockWidgetView.tsx +104 -0
- package/template/react/src/widgets/widget-router.ts +11 -0
- package/template/react/tsconfig.app.json +26 -0
- package/template/react/tsconfig.json +17 -0
- package/template/react/tsconfig.node.json +23 -0
- package/template/react/vite.config.ts +27 -0
- package/template/{widget.package.ts → react/widget.package.ts} +5 -1
- package/template/{.vscode → vue/.vscode}/extensions.json +3 -3
- package/template/vue/README.md +34 -0
- package/template/{env.d.ts → vue/env.d.ts} +1 -1
- package/template/{index.html → vue/index.html} +13 -13
- package/template/vue/package.json +34 -0
- package/template/vue/public/apple-touch-icon.png +0 -0
- package/template/vue/public/favicon-96x96.png +0 -0
- package/template/vue/public/favicon.ico +0 -0
- package/template/vue/public/favicon.svg +3 -0
- package/template/vue/public/logo.png +0 -0
- package/template/vue/public/preview_clock.png +0 -0
- package/template/vue/public/site.webmanifest +21 -0
- package/template/vue/public/web-app-manifest-192x192.png +0 -0
- package/template/vue/public/web-app-manifest-512x512.png +0 -0
- package/template/{src → vue/src}/App.vue +9 -9
- package/template/vue/src/Home.vue +10 -0
- package/template/{src → vue/src}/main.ts +13 -12
- package/template/{src → vue/src}/router/index.ts +4 -0
- package/template/{src → vue/src}/widgets/clock/Clock.widget.ts +21 -21
- package/template/{src → vue/src}/widgets/clock/ClockConfigView.vue +37 -38
- package/template/{src → vue/src}/widgets/clock/ClockWidgetRoutes.ts +28 -28
- package/template/{src → vue/src}/widgets/clock/ClockWidgetView.vue +38 -35
- package/template/{src → vue/src}/widgets/widget-router.ts +3 -3
- package/template/{tsconfig.app.json → vue/tsconfig.app.json} +13 -13
- package/template/{tsconfig.json → vue/tsconfig.json} +11 -11
- package/template/{tsconfig.node.json → vue/tsconfig.node.json} +17 -17
- package/template/vue/vite.config.ts +29 -0
- package/template/vue/widget.package.ts +24 -0
- package/unocss/src/main.ts +13 -0
- package/unocss/uno.config.ts +5 -0
- package/unocss/vite.config.ts +31 -0
- package/index.cjs +0 -7913
- package/template/README.md +0 -40
- package/template/package.json +0 -29
- package/template/public/favicon.ico +0 -0
- package/template/src/widgets/clock/model/ClockModel.ts +0 -5
- package/template/vite.config.ts +0 -15
- /package/template/{public → react/public}/preview_clock.png +0 -0
- /package/template/{src → vue/src}/assets/main.css +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Slider as SliderPrimitive } from "radix-ui"
|
|
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 SliderPrimitive.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
|
+
<SliderPrimitive.Root
|
|
26
|
+
data-slot="slider"
|
|
27
|
+
defaultValue={defaultValue}
|
|
28
|
+
value={value}
|
|
29
|
+
min={min}
|
|
30
|
+
max={max}
|
|
31
|
+
className={cn(
|
|
32
|
+
"relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col",
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
<SliderPrimitive.Track
|
|
38
|
+
data-slot="slider-track"
|
|
39
|
+
className="relative grow overflow-hidden rounded-full bg-muted data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1"
|
|
40
|
+
>
|
|
41
|
+
<SliderPrimitive.Range
|
|
42
|
+
data-slot="slider-range"
|
|
43
|
+
className="absolute bg-primary select-none data-horizontal:h-full data-vertical:w-full"
|
|
44
|
+
/>
|
|
45
|
+
</SliderPrimitive.Track>
|
|
46
|
+
{Array.from({ length: _values.length }, (_, index) => (
|
|
47
|
+
<SliderPrimitive.Thumb
|
|
48
|
+
data-slot="slider-thumb"
|
|
49
|
+
key={index}
|
|
50
|
+
className="relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50"
|
|
51
|
+
/>
|
|
52
|
+
))}
|
|
53
|
+
</SliderPrimitive.Root>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { Slider }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Switch as SwitchPrimitive } from "radix-ui"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
function Switch({
|
|
7
|
+
className,
|
|
8
|
+
size = "default",
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
|
11
|
+
size?: "sm" | "default"
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<SwitchPrimitive.Root
|
|
15
|
+
data-slot="switch"
|
|
16
|
+
data-size={size}
|
|
17
|
+
className={cn(
|
|
18
|
+
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
<SwitchPrimitive.Thumb
|
|
24
|
+
data-slot="switch-thumb"
|
|
25
|
+
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
|
26
|
+
/>
|
|
27
|
+
</SwitchPrimitive.Root>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { Switch }
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Tabs as TabsPrimitive } from "radix-ui"
|
|
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
|
+
className={cn(
|
|
17
|
+
"group/tabs flex gap-2 data-horizontal:flex-col",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const tabsListVariants = cva(
|
|
26
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
|
|
27
|
+
{
|
|
28
|
+
variants: {
|
|
29
|
+
variant: {
|
|
30
|
+
default: "bg-muted",
|
|
31
|
+
line: "gap-1 bg-transparent",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: {
|
|
35
|
+
variant: "default",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
function TabsList({
|
|
41
|
+
className,
|
|
42
|
+
variant = "default",
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
|
45
|
+
VariantProps<typeof tabsListVariants>) {
|
|
46
|
+
return (
|
|
47
|
+
<TabsPrimitive.List
|
|
48
|
+
data-slot="tabs-list"
|
|
49
|
+
data-variant={variant}
|
|
50
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function TabsTrigger({
|
|
57
|
+
className,
|
|
58
|
+
...props
|
|
59
|
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
60
|
+
return (
|
|
61
|
+
<TabsPrimitive.Trigger
|
|
62
|
+
data-slot="tabs-trigger"
|
|
63
|
+
className={cn(
|
|
64
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
65
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
|
66
|
+
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
|
67
|
+
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
|
68
|
+
className
|
|
69
|
+
)}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function TabsContent({
|
|
76
|
+
className,
|
|
77
|
+
...props
|
|
78
|
+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
79
|
+
return (
|
|
80
|
+
<TabsPrimitive.Content
|
|
81
|
+
data-slot="tabs-content"
|
|
82
|
+
className={cn("flex-1 text-sm outline-none", className)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { HashRouter } from 'react-router-dom'
|
|
4
|
+
import App from './App'
|
|
5
|
+
import '@widget-js/react/style.css'
|
|
6
|
+
import '@/assets/main.css'
|
|
7
|
+
|
|
8
|
+
const container = document.getElementById('root')
|
|
9
|
+
|
|
10
|
+
if (!container) {
|
|
11
|
+
throw new Error('Root container #root was not found.')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
createRoot(container).render(
|
|
15
|
+
<StrictMode>
|
|
16
|
+
<HashRouter>
|
|
17
|
+
<App />
|
|
18
|
+
</HashRouter>
|
|
19
|
+
</StrictMode>,
|
|
20
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Link } from 'react-router-dom'
|
|
2
|
+
import { widgets } from '@/widgets/widget-router'
|
|
3
|
+
|
|
4
|
+
export default function HomePage() {
|
|
5
|
+
return (
|
|
6
|
+
<main className="home-page">
|
|
7
|
+
<section className="hero-panel">
|
|
8
|
+
<p className="eyebrow">WidgetJS React Example</p>
|
|
9
|
+
<h1>Vue 示例已迁移为 React</h1>
|
|
10
|
+
<p className="hero-copy">
|
|
11
|
+
这个示例现在使用 Vite + React + TypeScript,并直接复用
|
|
12
|
+
<code>@widget-js/react</code>
|
|
13
|
+
提供的组件和 hooks。
|
|
14
|
+
</p>
|
|
15
|
+
</section>
|
|
16
|
+
|
|
17
|
+
<section className="widget-grid">
|
|
18
|
+
{widgets.map((widget) => {
|
|
19
|
+
const title = widget.title?.['zh-CN'] ?? widget.name
|
|
20
|
+
const widgetQuery = new URLSearchParams({
|
|
21
|
+
w_id: 'demo-clock',
|
|
22
|
+
w_name: widget.name,
|
|
23
|
+
w_title: title,
|
|
24
|
+
w_mode: '0',
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const configPath = widget.configPagePath?.split('?')[0] ?? '/widget/config/clock'
|
|
28
|
+
const configQuery = new URLSearchParams(widget.configPagePath?.split('?')[1] ?? '')
|
|
29
|
+
|
|
30
|
+
configQuery.set('w_id', 'demo-clock')
|
|
31
|
+
configQuery.set('w_name', widget.name)
|
|
32
|
+
configQuery.set('w_title', title)
|
|
33
|
+
configQuery.set('w_mode', '0')
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<article key={widget.name} className="widget-card">
|
|
37
|
+
<div className="widget-card__body">
|
|
38
|
+
<p className="widget-card__tag">React Widget</p>
|
|
39
|
+
<h2>{title}</h2>
|
|
40
|
+
<p>{widget.description?.['zh-CN'] ?? '一个最小可运行的 React 挂件示例。'}</p>
|
|
41
|
+
<dl className="widget-meta">
|
|
42
|
+
<div>
|
|
43
|
+
<dt>Widget</dt>
|
|
44
|
+
<dd>{widget.path}</dd>
|
|
45
|
+
</div>
|
|
46
|
+
<div>
|
|
47
|
+
<dt>Config</dt>
|
|
48
|
+
<dd>{configPath}</dd>
|
|
49
|
+
</div>
|
|
50
|
+
</dl>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="widget-card__actions">
|
|
53
|
+
<Link className="action-link action-link--primary" to={`${widget.path}?${widgetQuery.toString()}`}>
|
|
54
|
+
打开组件
|
|
55
|
+
</Link>
|
|
56
|
+
<Link className="action-link" to={`${configPath}?${configQuery.toString()}`}>
|
|
57
|
+
打开配置页
|
|
58
|
+
</Link>
|
|
59
|
+
</div>
|
|
60
|
+
</article>
|
|
61
|
+
)
|
|
62
|
+
})}
|
|
63
|
+
</section>
|
|
64
|
+
</main>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Widget, WidgetKeyword } from '@widget-js/core'
|
|
2
|
+
|
|
3
|
+
// TODO 修改组件信息,标题,描述,关键词
|
|
4
|
+
const ClockWidget = new Widget({
|
|
5
|
+
name: 'cn.example.widget.clock',
|
|
6
|
+
title: { 'zh-CN': '时钟' },
|
|
7
|
+
description: { 'zh-CN': 'Hello world!' },
|
|
8
|
+
keywords: [WidgetKeyword.RECOMMEND],
|
|
9
|
+
lang: 'zh-CN',
|
|
10
|
+
width: 3,
|
|
11
|
+
height: 3,
|
|
12
|
+
minWidth: 2,
|
|
13
|
+
maxWidth: 6,
|
|
14
|
+
minHeight: 2,
|
|
15
|
+
maxHeight: 6,
|
|
16
|
+
previewImage: '/preview_clock.png',
|
|
17
|
+
path: '/widget/clock',
|
|
18
|
+
configPagePath: '/widget/config/clock?frame=true&transparent=false&width=700&height=800',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export default ClockWidget
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { WidgetThemeOption } from '@widget-js/react'
|
|
2
|
+
import { useWidget, useWidgetStorage, useWidgetTheme, WidgetThemeForm, Window } from '@widget-js/react'
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import {
|
|
5
|
+
Field,
|
|
6
|
+
FieldContent,
|
|
7
|
+
FieldGroup,
|
|
8
|
+
FieldLabel,
|
|
9
|
+
} from '@/components/ui/field'
|
|
10
|
+
import { Input } from '@/components/ui/input'
|
|
11
|
+
import { Switch } from '@/components/ui/switch'
|
|
12
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
13
|
+
|
|
14
|
+
const clockThemeOption: WidgetThemeOption = {
|
|
15
|
+
useGlobalTheme: true,
|
|
16
|
+
borderRadius: true,
|
|
17
|
+
backgroundColor: true,
|
|
18
|
+
backgroundBorderColor: true,
|
|
19
|
+
primaryColor: true,
|
|
20
|
+
color: true,
|
|
21
|
+
} as const
|
|
22
|
+
|
|
23
|
+
export default function ClockConfigView() {
|
|
24
|
+
const { save } = useWidget()
|
|
25
|
+
const { widgetTheme, setWidgetTheme } = useWidgetTheme()
|
|
26
|
+
const [title, setTitle] = useWidgetStorage('title', 'Clock')
|
|
27
|
+
const [use24Hour, setUse24Hour] = useWidgetStorage('use-24-hour', true)
|
|
28
|
+
const [showSeconds, setShowSeconds] = useWidgetStorage('show-seconds', true)
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Window
|
|
32
|
+
title={title}
|
|
33
|
+
footer={(
|
|
34
|
+
<div className="flex flex-wrap justify-end gap-3">
|
|
35
|
+
<Button type="button" variant="outline" onClick={() => save()}>
|
|
36
|
+
应用
|
|
37
|
+
</Button>
|
|
38
|
+
<Button type="button" onClick={() => save({ closeWindow: true })}>
|
|
39
|
+
保存并关闭
|
|
40
|
+
</Button>
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
44
|
+
<Tabs defaultValue="content" className="gap-4">
|
|
45
|
+
<TabsList className="w-full justify-start">
|
|
46
|
+
<TabsTrigger value="content">组件设置</TabsTrigger>
|
|
47
|
+
<TabsTrigger value="theme">主题设置</TabsTrigger>
|
|
48
|
+
</TabsList>
|
|
49
|
+
|
|
50
|
+
<TabsContent
|
|
51
|
+
value="content"
|
|
52
|
+
className="rounded-xl border bg-card p-6 shadow-sm"
|
|
53
|
+
>
|
|
54
|
+
<FieldGroup>
|
|
55
|
+
<Field orientation="horizontal">
|
|
56
|
+
<FieldContent>
|
|
57
|
+
<FieldLabel htmlFor="clock-title" className="shrink-0 whitespace-nowrap md:justify-start">
|
|
58
|
+
标题
|
|
59
|
+
</FieldLabel>
|
|
60
|
+
</FieldContent>
|
|
61
|
+
<Input
|
|
62
|
+
className="w-32"
|
|
63
|
+
id="clock-title"
|
|
64
|
+
value={title}
|
|
65
|
+
placeholder="输入组件标题"
|
|
66
|
+
onChange={event => setTitle(event.target.value)}
|
|
67
|
+
/>
|
|
68
|
+
</Field>
|
|
69
|
+
|
|
70
|
+
<Field orientation="horizontal">
|
|
71
|
+
<FieldContent>
|
|
72
|
+
<FieldLabel htmlFor="clock-24-hour" className="shrink-0 whitespace-nowrap md:justify-start">
|
|
73
|
+
使用 24 小时制
|
|
74
|
+
</FieldLabel>
|
|
75
|
+
</FieldContent>
|
|
76
|
+
<Switch
|
|
77
|
+
id="clock-24-hour"
|
|
78
|
+
checked={use24Hour}
|
|
79
|
+
onCheckedChange={setUse24Hour}
|
|
80
|
+
/>
|
|
81
|
+
</Field>
|
|
82
|
+
|
|
83
|
+
<Field orientation="horizontal">
|
|
84
|
+
<FieldContent>
|
|
85
|
+
<FieldLabel htmlFor="clock-show-seconds" className="shrink-0 whitespace-nowrap md:justify-start">
|
|
86
|
+
显示秒钟
|
|
87
|
+
</FieldLabel>
|
|
88
|
+
</FieldContent>
|
|
89
|
+
<Switch
|
|
90
|
+
id="clock-show-seconds"
|
|
91
|
+
checked={showSeconds}
|
|
92
|
+
onCheckedChange={setShowSeconds}
|
|
93
|
+
/>
|
|
94
|
+
</Field>
|
|
95
|
+
</FieldGroup>
|
|
96
|
+
</TabsContent>
|
|
97
|
+
|
|
98
|
+
<TabsContent
|
|
99
|
+
value="theme"
|
|
100
|
+
className="rounded-xl border bg-card p-6 shadow-sm"
|
|
101
|
+
>
|
|
102
|
+
<WidgetThemeForm
|
|
103
|
+
showSectionHeader={false}
|
|
104
|
+
themeOption={clockThemeOption}
|
|
105
|
+
value={widgetTheme}
|
|
106
|
+
onChange={setWidgetTheme}
|
|
107
|
+
/>
|
|
108
|
+
</TabsContent>
|
|
109
|
+
</Tabs>
|
|
110
|
+
</Window>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RouteObject } from 'react-router-dom'
|
|
2
|
+
import { createElement } from 'react'
|
|
3
|
+
import ClockWidget from './Clock.widget'
|
|
4
|
+
import ClockConfigView from './ClockConfigView'
|
|
5
|
+
import ClockWidgetView from './ClockWidgetView'
|
|
6
|
+
|
|
7
|
+
const path = ClockWidget.path
|
|
8
|
+
const name = ClockWidget.name
|
|
9
|
+
|
|
10
|
+
const configPagePath = ClockWidget.configPagePath!.split('?')[0]!
|
|
11
|
+
|
|
12
|
+
const ClockWidgetRoutes: RouteObject[] = [
|
|
13
|
+
{
|
|
14
|
+
path,
|
|
15
|
+
id: `${name}`,
|
|
16
|
+
element: createElement(ClockWidgetView),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
path: configPagePath,
|
|
20
|
+
id: `${name}.config`,
|
|
21
|
+
element: createElement(ClockConfigView),
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
export default ClockWidgetRoutes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useWidget, useWidgetStorage, WidgetWrapper } from '@widget-js/react'
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
|
|
5
|
+
const ClockSection = styled.section`
|
|
6
|
+
display: grid;
|
|
7
|
+
place-items: center;
|
|
8
|
+
gap: 10px;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 100%;
|
|
11
|
+
padding: 24px;
|
|
12
|
+
color: var(--widget-color);
|
|
13
|
+
text-align: center;
|
|
14
|
+
user-select: none;
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
const Eyebrow = styled.p`
|
|
18
|
+
margin: 0;
|
|
19
|
+
color: color-mix(in srgb, var(--widget-primary-color, #4ed6ff) 70%, white);
|
|
20
|
+
font-size: 11px;
|
|
21
|
+
font-weight: 700;
|
|
22
|
+
letter-spacing: 0.18em;
|
|
23
|
+
text-transform: uppercase;
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
const Title = styled.h1`
|
|
27
|
+
margin: 0;
|
|
28
|
+
font-size: clamp(18px, 2vw, 26px);
|
|
29
|
+
font-weight: 700;
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
const TimeText = styled.div`
|
|
33
|
+
font-size: clamp(28px, 3.4vw, 46px);
|
|
34
|
+
font-weight: 800;
|
|
35
|
+
line-height: 1;
|
|
36
|
+
letter-spacing: 0.04em;
|
|
37
|
+
`
|
|
38
|
+
|
|
39
|
+
const DateText = styled.p`
|
|
40
|
+
margin: 0;
|
|
41
|
+
color: color-mix(in srgb, var(--widget-color, #fff) 72%, transparent);
|
|
42
|
+
font-size: 12px;
|
|
43
|
+
`
|
|
44
|
+
|
|
45
|
+
export default function ClockWidgetView() {
|
|
46
|
+
const { widgetParams, language } = useWidget()
|
|
47
|
+
const [title] = useWidgetStorage('title', 'Clock')
|
|
48
|
+
const [use24Hour] = useWidgetStorage('use-24-hour', true)
|
|
49
|
+
const [showSeconds] = useWidgetStorage('show-seconds', true)
|
|
50
|
+
const [now, setNow] = useState(() => new Date())
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const timer = window.setInterval(() => {
|
|
54
|
+
setNow(new Date())
|
|
55
|
+
}, 1000)
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
window.clearInterval(timer)
|
|
59
|
+
}
|
|
60
|
+
}, [])
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const previousHtmlBackground = document.documentElement.style.background
|
|
64
|
+
const previousBodyBackground = document.body.style.background
|
|
65
|
+
|
|
66
|
+
document.documentElement.style.background = 'transparent'
|
|
67
|
+
document.body.style.background = 'transparent'
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
document.documentElement.style.background = previousHtmlBackground
|
|
71
|
+
document.body.style.background = previousBodyBackground
|
|
72
|
+
}
|
|
73
|
+
}, [])
|
|
74
|
+
|
|
75
|
+
const locale = widgetParams.lang ?? language ?? 'zh-CN'
|
|
76
|
+
|
|
77
|
+
const timeText = useMemo(() => {
|
|
78
|
+
return new Intl.DateTimeFormat(locale, {
|
|
79
|
+
hour: '2-digit',
|
|
80
|
+
minute: '2-digit',
|
|
81
|
+
second: showSeconds ? '2-digit' : undefined,
|
|
82
|
+
hour12: !use24Hour,
|
|
83
|
+
}).format(now)
|
|
84
|
+
}, [locale, now, showSeconds, use24Hour])
|
|
85
|
+
|
|
86
|
+
const dateText = useMemo(() => {
|
|
87
|
+
return new Intl.DateTimeFormat(locale, {
|
|
88
|
+
weekday: 'long',
|
|
89
|
+
month: 'long',
|
|
90
|
+
day: 'numeric',
|
|
91
|
+
}).format(now)
|
|
92
|
+
}, [locale, now])
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<WidgetWrapper>
|
|
96
|
+
<ClockSection>
|
|
97
|
+
<Eyebrow>{widgetParams.title ?? 'WidgetJS React'}</Eyebrow>
|
|
98
|
+
<Title>{title}</Title>
|
|
99
|
+
<TimeText>{timeText}</TimeText>
|
|
100
|
+
<DateText>{dateText}</DateText>
|
|
101
|
+
</ClockSection>
|
|
102
|
+
</WidgetWrapper>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RouteObject } from 'react-router-dom'
|
|
2
|
+
import ClockWidget from './clock/Clock.widget'
|
|
3
|
+
import ClockWidgetRoutes from './clock/ClockWidgetRoutes'
|
|
4
|
+
|
|
5
|
+
export const widgets = [
|
|
6
|
+
ClockWidget,
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
export const widgetRoutes: RouteObject[] = [
|
|
10
|
+
...ClockWidgetRoutes,
|
|
11
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"types": ["vite/client"],
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"strict": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"noUncheckedSideEffectImports": true,
|
|
20
|
+
"baseUrl": ".",
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": ["env.d.ts", "src"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"verbatimModuleSyntax": true,
|
|
8
|
+
"moduleResolution": "Bundler",
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"erasableSyntaxOnly": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noUncheckedSideEffectImports": true
|
|
19
|
+
},
|
|
20
|
+
"include": [
|
|
21
|
+
"vite.config.ts"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { fileURLToPath, URL } from 'node:url'
|
|
2
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
3
|
+
import react from '@vitejs/plugin-react'
|
|
4
|
+
import widget from '@widget-js/vite-plugin-widget'
|
|
5
|
+
import { defineConfig } from 'vite'
|
|
6
|
+
|
|
7
|
+
export default defineConfig((config) => {
|
|
8
|
+
const offlineMode = config.mode === 'offline'
|
|
9
|
+
const base = offlineMode ? './' : '/clock'
|
|
10
|
+
return {
|
|
11
|
+
base,
|
|
12
|
+
plugins: [
|
|
13
|
+
tailwindcss(),
|
|
14
|
+
widget({
|
|
15
|
+
zipName: 'test-widget',
|
|
16
|
+
generateZip: offlineMode,
|
|
17
|
+
}),
|
|
18
|
+
react(),
|
|
19
|
+
|
|
20
|
+
],
|
|
21
|
+
resolve: {
|
|
22
|
+
alias: {
|
|
23
|
+
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
})
|
|
@@ -8,12 +8,16 @@ export default new WidgetPackage({
|
|
|
8
8
|
},
|
|
9
9
|
entry: '/',
|
|
10
10
|
hash: true,
|
|
11
|
+
remote: {
|
|
12
|
+
base: '/',
|
|
13
|
+
hostname: '修改成你的域名',
|
|
14
|
+
hash: true,
|
|
15
|
+
},
|
|
11
16
|
homepage: '',
|
|
12
17
|
name: 'cn.example.widget',
|
|
13
18
|
title: {
|
|
14
19
|
'zh-CN': '修改成你的组件标题',
|
|
15
20
|
},
|
|
16
|
-
version: '1.0.0',
|
|
17
21
|
devOptions: {
|
|
18
22
|
folder: './src/widgets/',
|
|
19
23
|
},
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
{
|
|
2
|
-
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
|
3
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
|
3
|
+
}
|