@wsxjs/wsx-base-components 0.0.5

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.
@@ -0,0 +1,241 @@
1
+ /** @jsxImportSource @wsxjs/wsx-core */
2
+ import { WebComponent, autoRegister, createLogger } from "@wsxjs/wsx-core";
3
+
4
+ const logger = createLogger("SvgDemo");
5
+
6
+ @autoRegister({ tagName: "svg-demo" })
7
+ export default class SvgDemo extends WebComponent {
8
+ private animationId: number | null = null;
9
+ private rotationAngle = 0;
10
+
11
+ constructor() {
12
+ super();
13
+ logger.info("SvgDemo component initialized");
14
+ }
15
+
16
+ render() {
17
+ return (
18
+ <div style="padding: 20px; background: #f5f5f5; border-radius: 8px; margin: 10px 0;">
19
+ <h3 style="margin-top: 0; color: #333;">SVG Showcase</h3>
20
+
21
+ {/* Basic SVG shapes */}
22
+ <div style="margin-bottom: 20px;">
23
+ <h4 style="margin: 10px 0; color: #666;">Basic Shapes</h4>
24
+ <svg
25
+ width="300"
26
+ height="100"
27
+ style="border: 1px solid #ddd; background: white;"
28
+ >
29
+ <circle
30
+ cx="50"
31
+ cy="50"
32
+ r="30"
33
+ fill="#e74c3c"
34
+ stroke="#c0392b"
35
+ strokeWidth="2"
36
+ />
37
+ <rect
38
+ x="100"
39
+ y="20"
40
+ width="60"
41
+ height="60"
42
+ fill="#3498db"
43
+ stroke="#2980b9"
44
+ strokeWidth="2"
45
+ rx="5"
46
+ />
47
+ <polygon
48
+ points="200,20 230,80 170,80"
49
+ fill="#2ecc71"
50
+ stroke="#27ae60"
51
+ strokeWidth="2"
52
+ />
53
+ <line
54
+ x1="250"
55
+ y1="20"
56
+ x2="290"
57
+ y2="80"
58
+ stroke="#9b59b6"
59
+ strokeWidth="3"
60
+ strokeLinecap="round"
61
+ />
62
+ </svg>
63
+ </div>
64
+
65
+ {/* SVG with gradients */}
66
+ <div style="margin-bottom: 20px;">
67
+ <h4 style="margin: 10px 0; color: #666;">Gradients & Effects</h4>
68
+ <svg
69
+ width="300"
70
+ height="100"
71
+ style="border: 1px solid #ddd; background: white;"
72
+ >
73
+ <defs>
74
+ <linearGradient id="blueGradient" x1="0%" y1="0%" x2="100%" y2="0%">
75
+ <stop offset="0%" stopColor="#3498db" />
76
+ <stop offset="100%" stopColor="#9b59b6" />
77
+ </linearGradient>
78
+ <radialGradient id="redGradient" cx="50%" cy="50%" r="50%">
79
+ <stop offset="0%" stopColor="#e74c3c" />
80
+ <stop offset="100%" stopColor="#c0392b" />
81
+ </radialGradient>
82
+ </defs>
83
+ <rect
84
+ x="20"
85
+ y="20"
86
+ width="120"
87
+ height="60"
88
+ fill="url(#blueGradient)"
89
+ rx="10"
90
+ />
91
+ <circle cx="200" cy="50" r="35" fill="url(#redGradient)" />
92
+ </svg>
93
+ </div>
94
+
95
+ {/* Animated SVG */}
96
+ <div style="margin-bottom: 20px;">
97
+ <h4 style="margin: 10px 0; color: #666;">Animation</h4>
98
+ <svg
99
+ width="300"
100
+ height="100"
101
+ style="border: 1px solid #ddd; background: white;"
102
+ >
103
+ <g transform={`translate(150, 50) rotate(${this.rotationAngle})`}>
104
+ <polygon
105
+ points="-30,0 0,-40 30,0 0,40"
106
+ fill="#f39c12"
107
+ stroke="#e67e22"
108
+ strokeWidth="2"
109
+ />
110
+ <circle cx="0" cy="0" r="8" fill="#2c3e50" />
111
+ </g>
112
+ </svg>
113
+ <div style="margin-top: 10px;">
114
+ <button
115
+ onClick={this.startAnimation}
116
+ style="margin-right: 10px; padding: 5px 10px;"
117
+ >
118
+ Start Animation
119
+ </button>
120
+ <button onClick={this.stopAnimation} style="padding: 5px 10px;">
121
+ Stop Animation
122
+ </button>
123
+ </div>
124
+ </div>
125
+
126
+ {/* Interactive SVG chart */}
127
+ <div style="margin-bottom: 20px;">
128
+ <h4 style="margin: 10px 0; color: #666;">Interactive Chart</h4>
129
+ {this.renderChart()}
130
+ </div>
131
+
132
+ {/* SVG Icons */}
133
+ <div>
134
+ <h4 style="margin: 10px 0; color: #666;">Icon Components</h4>
135
+ <div style="display: flex; gap: 15px; align-items: center;">
136
+ <svg-icon name="star" size="32" color="#f39c12"></svg-icon>
137
+ <svg-icon name="heart" size="32" color="#e74c3c"></svg-icon>
138
+ <svg-icon name="check" size="32" color="#27ae60"></svg-icon>
139
+ <svg-icon name="github" size="32" color="#333"></svg-icon>
140
+ <svg-icon name="play" size="32" color="#3498db"></svg-icon>
141
+ <svg-icon name="settings" size="32" color="#9b59b6"></svg-icon>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ );
146
+ }
147
+
148
+ private renderChart() {
149
+ const data = [30, 80, 45, 60, 20, 90, 55];
150
+ const maxValue = Math.max(...data);
151
+ const barWidth = 30;
152
+ const barSpacing = 40;
153
+ const chartHeight = 120;
154
+ const chartWidth = data.length * barSpacing + 40;
155
+
156
+ return (
157
+ <svg
158
+ width={chartWidth}
159
+ height={chartHeight + 40}
160
+ style="border: 1px solid #ddd; background: white;"
161
+ >
162
+ {/* Chart bars */}
163
+ {data.map((value, index) => {
164
+ const barHeight = (value / maxValue) * chartHeight;
165
+ const x = index * barSpacing + 20;
166
+ const y = chartHeight - barHeight + 20;
167
+
168
+ return (
169
+ <g key={index}>
170
+ <rect
171
+ x={x}
172
+ y={y}
173
+ width={barWidth}
174
+ height={barHeight}
175
+ fill="#3498db"
176
+ stroke="#2980b9"
177
+ strokeWidth="1"
178
+ onMouseEnter={(e) => this.showTooltip(e, value)}
179
+ onMouseLeave={this.hideTooltip}
180
+ style="cursor: pointer; transition: fill 0.2s;"
181
+ />
182
+ <text
183
+ x={x + barWidth / 2}
184
+ y={chartHeight + 35}
185
+ textAnchor="middle"
186
+ fontSize="12"
187
+ fill="#666"
188
+ >
189
+ {index + 1}
190
+ </text>
191
+ </g>
192
+ );
193
+ })}
194
+ </svg>
195
+ );
196
+ }
197
+
198
+ private showTooltip = (event: Event, value: number) => {
199
+ const rect = event.target as SVGRectElement;
200
+ rect.setAttribute("fill", "#e74c3c");
201
+
202
+ // You could create a proper tooltip here
203
+ logger.debug(`Tooltip value: ${value}`);
204
+ };
205
+
206
+ private hideTooltip = (event: Event) => {
207
+ const rect = event.target as SVGRectElement;
208
+ rect.setAttribute("fill", "#3498db");
209
+ };
210
+
211
+ private startAnimation = () => {
212
+ if (this.animationId) return;
213
+
214
+ const animate = () => {
215
+ this.rotationAngle += 2;
216
+ if (this.rotationAngle >= 360) {
217
+ this.rotationAngle = 0;
218
+ }
219
+ this.rerender();
220
+ this.animationId = requestAnimationFrame(animate);
221
+ };
222
+
223
+ this.animationId = requestAnimationFrame(animate);
224
+ };
225
+
226
+ private stopAnimation = () => {
227
+ if (this.animationId) {
228
+ cancelAnimationFrame(this.animationId);
229
+ this.animationId = null;
230
+ }
231
+ };
232
+
233
+ protected onConnected(): void {
234
+ logger.info("SvgDemo connected to DOM");
235
+ }
236
+
237
+ protected onDisconnected(): void {
238
+ logger.info("SvgDemo disconnected from DOM");
239
+ this.stopAnimation();
240
+ }
241
+ }
@@ -0,0 +1,88 @@
1
+ /** @jsxImportSource @wsxjs/wsx-core */
2
+ import { WebComponent, autoRegister, createLogger } from "@wsxjs/wsx-core";
3
+
4
+ const logger = createLogger("SvgIcon");
5
+
6
+ @autoRegister({ tagName: "svg-icon" })
7
+ export default class SvgIcon extends WebComponent {
8
+ constructor() {
9
+ super();
10
+ logger.info("SvgIcon component initialized");
11
+ }
12
+
13
+ render() {
14
+ const size = this.getAttribute("size") || "24";
15
+ const color = this.getAttribute("color") || "currentColor";
16
+ const name = this.getAttribute("name") || "star";
17
+
18
+ // Different icon paths based on name
19
+ const iconPaths = {
20
+ star: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
21
+ heart: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z",
22
+ check: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z",
23
+ close: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
24
+ github: "M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22",
25
+ play: "M8 5v14l11-7z",
26
+ settings:
27
+ "M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1 1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z",
28
+ };
29
+
30
+ const path = iconPaths[name as keyof typeof iconPaths] || iconPaths.star;
31
+
32
+ return (
33
+ <svg
34
+ width={size}
35
+ height={size}
36
+ viewBox="0 0 24 24"
37
+ fill="none"
38
+ stroke={color}
39
+ strokeWidth="2"
40
+ strokeLinecap="round"
41
+ strokeLinejoin="round"
42
+ className="svg-icon"
43
+ onClick={this.handleClick}
44
+ style="cursor: pointer; transition: transform 0.2s ease; display: inline-block;"
45
+ onMouseEnter={this.handleMouseEnter}
46
+ onMouseLeave={this.handleMouseLeave}
47
+ >
48
+ <path d={path} fill={color} />
49
+ </svg>
50
+ );
51
+ }
52
+
53
+ private handleClick = (event: Event) => {
54
+ logger.debug("SVG icon clicked", { name: this.getAttribute("name") });
55
+
56
+ // Dispatch custom event
57
+ this.dispatchEvent(
58
+ new CustomEvent("icon-click", {
59
+ detail: {
60
+ name: this.getAttribute("name"),
61
+ originalEvent: event,
62
+ },
63
+ bubbles: true,
64
+ })
65
+ );
66
+ };
67
+
68
+ private handleMouseEnter = (event: Event) => {
69
+ const svg = event.target as SVGElement;
70
+ svg.style.transform = "scale(1.1)";
71
+ };
72
+
73
+ private handleMouseLeave = (event: Event) => {
74
+ const svg = event.target as SVGElement;
75
+ svg.style.transform = "scale(1)";
76
+ };
77
+
78
+ static get observedAttributes() {
79
+ return ["name", "size", "color"];
80
+ }
81
+
82
+ protected onAttributeChanged(name: string, oldValue: string, newValue: string): void {
83
+ logger.debug(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
84
+ if (this.connected) {
85
+ this.rerender();
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,91 @@
1
+ /* ThemeSwitcher Component Styles */
2
+
3
+ :host {
4
+ display: block;
5
+ }
6
+
7
+ .theme-switcher-container {
8
+ position: relative;
9
+ display: flex;
10
+ align-items: center;
11
+ }
12
+
13
+ .theme-switcher-btn {
14
+ width: var(--theme-switcher-width, 2.5rem);
15
+ height: var(--theme-switcher-height, 2.5rem);
16
+ padding: var(--theme-switcher-padding, 0.5rem);
17
+ border-radius: var(--theme-switcher-border-radius, 8px);
18
+ background: var(--theme-switcher-bg, #dc2626);
19
+ border: var(--theme-switcher-border, none);
20
+ color: var(--theme-switcher-color, white);
21
+ cursor: pointer;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ transition: all var(--theme-switcher-transition, 0.3s ease);
26
+ box-shadow: var(--theme-switcher-shadow, 0 4px 15px rgba(220, 38, 38, 0.4));
27
+ font-weight: var(--theme-switcher-font-weight, 600);
28
+ font-family: var(--theme-switcher-font-family, inherit);
29
+ }
30
+
31
+ .theme-switcher-btn:hover {
32
+ background: var(--theme-switcher-hover-bg, #b91c1c);
33
+ transform: var(--theme-switcher-hover-transform, translateY(-2px));
34
+ box-shadow: var(--theme-switcher-hover-shadow, 0 8px 25px rgba(220, 38, 38, 0.5));
35
+ }
36
+
37
+ .theme-switcher-btn:active {
38
+ transform: var(--theme-switcher-active-transform, translateY(0));
39
+ }
40
+
41
+ .theme-switcher-icon {
42
+ font-size: var(--theme-switcher-icon-size, 1rem);
43
+ line-height: 1;
44
+ transition: transform var(--theme-switcher-icon-transition, 0.3s ease);
45
+ }
46
+
47
+ .theme-switcher-btn:hover .theme-switcher-icon {
48
+ transform: var(--theme-switcher-icon-hover-transform, rotate(360deg));
49
+ }
50
+
51
+ /* Light theme specific styles */
52
+ .theme-switcher-btn[data-theme="light"] {
53
+ background: var(--theme-switcher-light-bg, #dc2626);
54
+ color: var(--theme-switcher-light-color, white);
55
+ }
56
+
57
+ .theme-switcher-btn[data-theme="light"]:hover {
58
+ background: var(--theme-switcher-light-hover-bg, #b91c1c);
59
+ }
60
+
61
+ /* Dark theme specific styles */
62
+ .theme-switcher-btn[data-theme="dark"] {
63
+ background: var(--theme-switcher-dark-bg, #dc2626);
64
+ color: var(--theme-switcher-dark-color, white);
65
+ }
66
+
67
+ .theme-switcher-btn[data-theme="dark"]:hover {
68
+ background: var(--theme-switcher-dark-hover-bg, #b91c1c);
69
+ }
70
+
71
+ /* Auto theme specific styles */
72
+ .theme-switcher-btn[data-theme="auto"] {
73
+ background: var(--theme-switcher-auto-bg, linear-gradient(135deg, #dc2626, #b91c1c));
74
+ color: var(--theme-switcher-auto-color, white);
75
+ }
76
+
77
+ .theme-switcher-btn[data-theme="auto"]:hover {
78
+ background: var(--theme-switcher-auto-hover-bg, linear-gradient(135deg, #b91c1c, #991b1b));
79
+ }
80
+
81
+ /* Responsive design */
82
+ @media (max-width: 768px) {
83
+ .theme-switcher-btn {
84
+ width: var(--theme-switcher-mobile-width, 2rem);
85
+ height: var(--theme-switcher-mobile-height, 2rem);
86
+ }
87
+
88
+ .theme-switcher-icon {
89
+ font-size: var(--theme-switcher-mobile-icon-size, 0.9rem);
90
+ }
91
+ }
@@ -0,0 +1,97 @@
1
+ /** @jsxImportSource @wsxjs/wsx-core */
2
+
3
+ import { WebComponent, autoRegister } from "@wsxjs/wsx-core";
4
+ import styles from "./ThemeSwitcher.css?inline";
5
+
6
+ @autoRegister({ tagName: "theme-switcher" })
7
+ export default class ThemeSwitcher extends WebComponent {
8
+ private currentTheme: "light" | "dark" | "auto" = "auto";
9
+
10
+ constructor() {
11
+ super({
12
+ styles,
13
+ styleName: "theme-switcher",
14
+ });
15
+
16
+ this.initTheme();
17
+ }
18
+
19
+ render(): HTMLElement {
20
+ return (
21
+ <div class="theme-switcher-container">
22
+ <button
23
+ class="theme-switcher-btn"
24
+ data-theme={this.currentTheme}
25
+ onClick={this.toggleTheme}
26
+ title={`当前主题: ${this.getThemeLabel()}`}
27
+ >
28
+ <span class="theme-switcher-icon">{this.getThemeIcon()}</span>
29
+ </button>
30
+ </div>
31
+ );
32
+ }
33
+
34
+ private getThemeIcon(): string {
35
+ // 根据当前实际应用的主题显示图标,而不是设置的主题
36
+ const html = document.documentElement;
37
+ const isDark = html.classList.contains("dark");
38
+
39
+ if (this.currentTheme === "auto") {
40
+ // 自动模式:根据系统主题显示对应图标
41
+ return isDark ? "🌙" : "☀️";
42
+ } else if (this.currentTheme === "light") {
43
+ return "☀️";
44
+ } else {
45
+ return "🌙";
46
+ }
47
+ }
48
+
49
+ private getThemeLabel(): string {
50
+ const labels = {
51
+ light: "浅色",
52
+ dark: "深色",
53
+ auto: "自动",
54
+ };
55
+
56
+ return labels[this.currentTheme];
57
+ }
58
+
59
+ private toggleTheme = (): void => {
60
+ const themes: ("light" | "dark" | "auto")[] = ["auto", "light", "dark"];
61
+ const currentIndex = themes.indexOf(this.currentTheme);
62
+ const nextIndex = (currentIndex + 1) % themes.length;
63
+ this.setTheme(themes[nextIndex]);
64
+ };
65
+
66
+ private setTheme(theme: "light" | "dark" | "auto"): void {
67
+ this.currentTheme = theme;
68
+ const html = document.documentElement;
69
+
70
+ if (theme === "auto") {
71
+ html.removeAttribute("class");
72
+ this.checkSystemTheme();
73
+ } else {
74
+ html.className = theme;
75
+ }
76
+
77
+ localStorage.setItem("wsx-theme", theme);
78
+ this.rerender();
79
+ }
80
+
81
+ private checkSystemTheme(): void {
82
+ const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
83
+ document.documentElement.className = isDark ? "dark" : "";
84
+ }
85
+
86
+ private initTheme(): void {
87
+ const savedTheme =
88
+ (localStorage.getItem("wsx-theme") as "light" | "dark" | "auto") || "auto";
89
+ this.setTheme(savedTheme);
90
+
91
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
92
+ if (this.currentTheme === "auto") {
93
+ document.documentElement.className = e.matches ? "dark" : "";
94
+ }
95
+ });
96
+ }
97
+ }