create-widget 24.1.1-beta.9 → 26.7.22

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 (83) hide show
  1. package/LICENSE +27 -27
  2. package/bin/index.js +216 -0
  3. package/eslint/eslint.config.js +12 -0
  4. package/package.json +24 -15
  5. package/readme.md +80 -0
  6. package/template/react/README.md +41 -0
  7. package/template/react/components.json +25 -0
  8. package/template/react/env.d.ts +1 -0
  9. package/template/react/index.html +13 -0
  10. package/template/react/package.json +42 -0
  11. package/template/react/public/apple-touch-icon.png +0 -0
  12. package/template/react/public/favicon-96x96.png +0 -0
  13. package/template/react/public/favicon.ico +0 -0
  14. package/template/react/public/favicon.svg +3 -0
  15. package/template/react/public/logo.png +0 -0
  16. package/template/react/public/site.webmanifest +21 -0
  17. package/template/react/public/web-app-manifest-192x192.png +0 -0
  18. package/template/react/public/web-app-manifest-512x512.png +0 -0
  19. package/template/{public → react/public}/widget.json +55 -50
  20. package/template/react/src/App.tsx +17 -0
  21. package/template/react/src/assets/main.css +221 -0
  22. package/template/react/src/components/ui/button.tsx +67 -0
  23. package/template/react/src/components/ui/card.tsx +103 -0
  24. package/template/react/src/components/ui/color-picker.tsx +171 -0
  25. package/template/react/src/components/ui/field.tsx +238 -0
  26. package/template/react/src/components/ui/input.tsx +19 -0
  27. package/template/react/src/components/ui/label.tsx +22 -0
  28. package/template/react/src/components/ui/separator.tsx +28 -0
  29. package/template/react/src/components/ui/slider.tsx +57 -0
  30. package/template/react/src/components/ui/switch.tsx +31 -0
  31. package/template/react/src/components/ui/tabs.tsx +88 -0
  32. package/template/react/src/lib/utils.ts +7 -0
  33. package/template/react/src/main.tsx +20 -0
  34. package/template/react/src/pages/home-page.tsx +66 -0
  35. package/template/react/src/widgets/clock/Clock.widget.ts +21 -0
  36. package/template/react/src/widgets/clock/ClockConfigView.tsx +112 -0
  37. package/template/react/src/widgets/clock/ClockWidgetRoutes.ts +25 -0
  38. package/template/react/src/widgets/clock/ClockWidgetView.tsx +104 -0
  39. package/template/react/src/widgets/widget-router.ts +11 -0
  40. package/template/react/tsconfig.app.json +26 -0
  41. package/template/react/tsconfig.json +17 -0
  42. package/template/react/tsconfig.node.json +23 -0
  43. package/template/react/vite.config.ts +27 -0
  44. package/template/{widget.package.ts → react/widget.package.ts} +5 -1
  45. package/template/{.vscode → vue/.vscode}/extensions.json +3 -3
  46. package/template/vue/README.md +34 -0
  47. package/template/{env.d.ts → vue/env.d.ts} +1 -1
  48. package/template/{index.html → vue/index.html} +13 -13
  49. package/template/vue/package.json +34 -0
  50. package/template/vue/public/apple-touch-icon.png +0 -0
  51. package/template/vue/public/favicon-96x96.png +0 -0
  52. package/template/vue/public/favicon.ico +0 -0
  53. package/template/vue/public/favicon.svg +3 -0
  54. package/template/vue/public/logo.png +0 -0
  55. package/template/vue/public/preview_clock.png +0 -0
  56. package/template/vue/public/site.webmanifest +21 -0
  57. package/template/vue/public/web-app-manifest-192x192.png +0 -0
  58. package/template/vue/public/web-app-manifest-512x512.png +0 -0
  59. package/template/{src → vue/src}/App.vue +9 -9
  60. package/template/vue/src/Home.vue +10 -0
  61. package/template/{src → vue/src}/main.ts +13 -12
  62. package/template/{src → vue/src}/router/index.ts +4 -0
  63. package/template/{src → vue/src}/widgets/clock/Clock.widget.ts +21 -21
  64. package/template/{src → vue/src}/widgets/clock/ClockConfigView.vue +37 -38
  65. package/template/{src → vue/src}/widgets/clock/ClockWidgetRoutes.ts +28 -28
  66. package/template/{src → vue/src}/widgets/clock/ClockWidgetView.vue +38 -35
  67. package/template/{src → vue/src}/widgets/widget-router.ts +3 -3
  68. package/template/{tsconfig.app.json → vue/tsconfig.app.json} +13 -13
  69. package/template/{tsconfig.json → vue/tsconfig.json} +11 -11
  70. package/template/{tsconfig.node.json → vue/tsconfig.node.json} +17 -17
  71. package/template/vue/vite.config.ts +29 -0
  72. package/template/vue/widget.package.ts +24 -0
  73. package/unocss/src/main.ts +13 -0
  74. package/unocss/uno.config.ts +5 -0
  75. package/unocss/vite.config.ts +31 -0
  76. package/index.cjs +0 -7913
  77. package/template/README.md +0 -40
  78. package/template/package.json +0 -29
  79. package/template/public/favicon.ico +0 -0
  80. package/template/src/widgets/clock/model/ClockModel.ts +0 -5
  81. package/template/vite.config.ts +0 -15
  82. /package/template/{public → react/public}/preview_clock.png +0 -0
  83. /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,7 @@
1
+ import type { ClassValue } from 'clsx'
2
+ import { clsx } from 'clsx'
3
+ import { twMerge } from 'tailwind-merge'
4
+
5
+ export function cn(...inputs: ClassValue[]) {
6
+ return twMerge(clsx(inputs))
7
+ }
@@ -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,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "@/*": ["./src/*"]
6
+ }
7
+ },
8
+ "references": [
9
+ {
10
+ "path": "./tsconfig.node.json"
11
+ },
12
+ {
13
+ "path": "./tsconfig.app.json"
14
+ }
15
+ ],
16
+ "files": []
17
+ }
@@ -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
+ }