lupine.components 1.1.41 → 1.1.43

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 (37) hide show
  1. package/package.json +1 -1
  2. package/src/component-pool/charts/area-chart-demo.tsx +0 -4
  3. package/src/component-pool/charts/area-chart.tsx +5 -2
  4. package/src/component-pool/charts/bar-chart-demo.tsx +1 -4
  5. package/src/component-pool/charts/bar-chart.tsx +5 -2
  6. package/src/component-pool/charts/chart-utils.ts +4 -3
  7. package/src/component-pool/charts/column-chart-demo.tsx +1 -4
  8. package/src/component-pool/charts/column-chart.tsx +5 -2
  9. package/src/component-pool/charts/donut-chart-demo.tsx +2 -5
  10. package/src/component-pool/charts/donut-chart.tsx +1 -23
  11. package/src/component-pool/charts/gauge-chart-demo.tsx +0 -4
  12. package/src/component-pool/charts/gauge-chart.tsx +5 -2
  13. package/src/component-pool/charts/line-chart-demo.tsx +0 -4
  14. package/src/component-pool/charts/line-chart.tsx +5 -2
  15. package/src/component-pool/charts/pie-chart-demo.tsx +4 -10
  16. package/src/component-pool/charts/pie-chart.tsx +78 -61
  17. package/src/component-pool/charts/radar-chart-demo.tsx +0 -3
  18. package/src/component-pool/charts/radar-chart.tsx +40 -5
  19. package/src/component-pool/charts/scatter-chart-demo.tsx +0 -3
  20. package/src/component-pool/charts/scatter-chart.tsx +5 -2
  21. package/src/component-pool/index.ts +1 -0
  22. package/src/component-pool/svg-graph/index.ts +1 -0
  23. package/src/component-pool/svg-graph/svg-graph-demo.tsx +59 -0
  24. package/src/component-pool/svg-graph/svg-graph.ts +166 -0
  25. package/src/component-pool/youtube-player/youtube-player-demo.tsx +4 -4
  26. package/src/component-pool/youtube-player/youtube-player.tsx +90 -14
  27. package/src/components/action-sheet-demo.tsx +12 -0
  28. package/src/components/action-sheet-theme.tsx +150 -0
  29. package/src/components/index.ts +1 -0
  30. package/src/components/menu-bar.tsx +15 -4
  31. package/src/components/menu-sidebar.tsx +24 -12
  32. package/src/components/message-box.tsx +18 -0
  33. package/src/components/popup-menu.tsx +4 -4
  34. package/src/components/tabs.tsx +9 -6
  35. package/src/demo/demo-about.tsx +1 -1
  36. package/src/demo/demo-frame-helper.tsx +8 -1
  37. package/src/demo/demo-registry.ts +2 -0
@@ -105,8 +105,20 @@ export const RadarChart = (props: RadarChartProps) => {
105
105
 
106
106
  const pointsStr = coordinates.map((c) => `${c.x},${c.y}`).join(' ');
107
107
 
108
+ const seriesGroupElements: any[] = [];
109
+
108
110
  // Fill area
109
- dataElements.push(<polygon points={pointsStr} fill={color} fillOpacity='0.3' stroke={color} strokeWidth='2' />);
111
+ seriesGroupElements.push(
112
+ <polygon
113
+ class="radar-polygon"
114
+ points={pointsStr}
115
+ fill={color}
116
+ fillOpacity='0.15'
117
+ stroke={color}
118
+ strokeWidth='2'
119
+ style={{ transition: 'fill-opacity 0.2s ease' }}
120
+ />
121
+ );
110
122
 
111
123
  // Points and Tooltips
112
124
  coordinates.forEach((c) => {
@@ -132,9 +144,9 @@ export const RadarChart = (props: RadarChartProps) => {
132
144
  e.target.setAttribute('r', '4');
133
145
  };
134
146
 
135
- dataElements.push(
147
+ seriesGroupElements.push(
136
148
  <circle
137
- class='chart-element'
149
+ class='chart-element radar-point'
138
150
  cx={c.x}
139
151
  cy={c.y}
140
152
  r='4'
@@ -147,6 +159,26 @@ export const RadarChart = (props: RadarChartProps) => {
147
159
  />
148
160
  );
149
161
  });
162
+
163
+ const handleGroupMouseEnter = (e: any) => {
164
+ const polygon = e.currentTarget.querySelector('.radar-polygon');
165
+ if (polygon) polygon.setAttribute('fill-opacity', '0.6');
166
+ };
167
+
168
+ const handleGroupMouseLeave = (e: any) => {
169
+ const polygon = e.currentTarget.querySelector('.radar-polygon');
170
+ if (polygon) polygon.setAttribute('fill-opacity', '0.15');
171
+ };
172
+
173
+ dataElements.push(
174
+ <g
175
+ class="radar-series"
176
+ onMouseEnter={handleGroupMouseEnter}
177
+ onMouseLeave={handleGroupMouseLeave}
178
+ >
179
+ {seriesGroupElements}
180
+ </g>
181
+ );
150
182
  });
151
183
 
152
184
  return dataElements;
@@ -156,13 +188,16 @@ export const RadarChart = (props: RadarChartProps) => {
156
188
  globalCssId,
157
189
  };
158
190
 
159
- const styleStr = `width: ${props.width || '100%'}; height: ${props.height || '100%'};`;
191
+ const ratio = props.aspectRatio ?? 16 / 9;
192
+ const paddingTop = `${(1 / ratio) * 100}%`;
193
+
194
+ const styleStr = `width: ${props.width || '100%'};`;
160
195
 
161
196
  return (
162
197
  <div ref={ref} class='&-container' style={styleStr}>
163
198
  {props.title && <div class='chart-title'>{props.title}</div>}
164
199
 
165
- <div style={{ flex: 1, position: 'relative', display: 'flex', justifyContent: 'center', minHeight: 0 }}>
200
+ <div style={{ flex: 1, position: 'relative', display: 'flex', justifyContent: 'center', minHeight: 0, paddingTop }}>
166
201
  <svg
167
202
  class='chart-svg'
168
203
  style={{
@@ -16,13 +16,11 @@ export const scatterChartDemo: DemoStory<any> = {
16
16
  args: {
17
17
  title: 'Value Correlation',
18
18
  width: '100%',
19
- height: '350px',
20
19
  showLegend: true,
21
20
  },
22
21
  argTypes: {
23
22
  title: { control: 'text' },
24
23
  width: { control: 'text' },
25
- height: { control: 'text' },
26
24
  showLegend: { control: 'boolean' },
27
25
  },
28
26
  render: (args: any) => {
@@ -68,7 +66,6 @@ export const scatterChartDemo: DemoStory<any> = {
68
66
  data={correlationData}
69
67
  title={args.title}
70
68
  width={args.width}
71
- height={args.height}
72
69
  showLegend={args.showLegend}
73
70
  />
74
71
  </div>
@@ -211,13 +211,16 @@ export const ScatterChart = (props: ScatterChartProps) => {
211
211
  },
212
212
  };
213
213
 
214
- const styleStr = `width: ${props.width || '100%'}; height: ${props.height || '100%'};`;
214
+ const ratio = props.aspectRatio ?? 16 / 9;
215
+ const paddingTop = `${(1 / ratio) * 100}%`;
216
+
217
+ const styleStr = `width: ${props.width || '100%'};`;
215
218
 
216
219
  return (
217
220
  <div ref={ref} class='&-container' style={styleStr}>
218
221
  {props.title && <div class='chart-title'>{props.title}</div>}
219
222
 
220
- <div style={{ flex: 1, position: 'relative', minHeight: 0 }}>{chartVar.node}</div>
223
+ <div style={{ flex: 1, position: 'relative', minHeight: 0, paddingTop }}>{chartVar.node}</div>
221
224
 
222
225
  {showLegend && (
223
226
  <div class='chart-legend'>
@@ -5,6 +5,7 @@ export * from './breadcrumbs';
5
5
  export * from './card';
6
6
  export * from './carousel';
7
7
  export * from './cascader';
8
+ export * from './charts';
8
9
  export * from './copy-button';
9
10
  export * from './date-picker';
10
11
  export * from './floating-icon-menu';
@@ -0,0 +1 @@
1
+ export * from './svg-graph';
@@ -0,0 +1,59 @@
1
+ import { RefProps } from 'lupine.web';
2
+ import { SvgGraph } from './svg-graph';
3
+ import { DemoStory } from '../../demo/demo-types';
4
+
5
+ const SvgGraphDemo = () => {
6
+ const ref: RefProps = {
7
+ onLoad: async (el) => {
8
+ const graph = new SvgGraph(el as HTMLElement);
9
+
10
+ // Draw a line
11
+ graph.drawLine('red', '2px', 50, 50, 250, 50, 'line1');
12
+
13
+ // Draw a rect
14
+ graph.draw('rect', '#e0f7fa', '#00acc1', '2px', 50, 100, 100, 50, 'rect1');
15
+
16
+ // Draw an ellipse
17
+ graph.draw('ellipse', '#fce4ec', '#d81b60', '2px', 200, 100, 100, 50, 'ellipse1');
18
+
19
+ // Draw a rounded rect
20
+ graph.draw('roundrect', '#e8f5e9', '#43a047', '2px', 50, 200, 150, 60, 'roundrect1');
21
+
22
+ // Test resize after 1s
23
+ setTimeout(() => {
24
+ graph.resize('line1', 50, 50, 300, 100);
25
+ }, 1000);
26
+ },
27
+ };
28
+
29
+ return (
30
+ <div style={{ padding: '16px' }}>
31
+ <h3>SvgGraph Component Demo</h3>
32
+ <p>This demonstrates the imperative SVG Graph API modernized from legacy JGraph.</p>
33
+
34
+ <div
35
+ ref={ref}
36
+ style={{
37
+ position: 'relative',
38
+ width: '100%',
39
+ height: '400px',
40
+ border: '1px solid #ccc',
41
+ marginTop: '16px',
42
+ backgroundColor: '#fafafa',
43
+ }}
44
+ >
45
+ {/* SVG will be injected here */}
46
+ </div>
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export const svgGraphDemo: DemoStory<any> = {
52
+ id: 'svg-graph-demo',
53
+ text: 'Svg Graph',
54
+ args: {},
55
+ render: (args: any) => {
56
+ return <SvgGraphDemo />;
57
+ },
58
+ code: 'import { SvgGraph } from "lupine.components/src/component-pool/svg-graph";\n// Check source code for usage.',
59
+ };
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Modernized SVG Graph Library
3
+ * Replaces the legacy JGraph library.
4
+ */
5
+
6
+ export class SvgGraph {
7
+ public svgRoot: SVGSVGElement;
8
+ private elements: Map<string, SVGElement> = new Map();
9
+
10
+ constructor(container: HTMLElement | string) {
11
+ let el: HTMLElement | null = null;
12
+ if (typeof container === 'string') {
13
+ el = document.getElementById(container);
14
+ } else {
15
+ el = container;
16
+ }
17
+
18
+ if (!el) {
19
+ throw new Error('SvgGraph: container not found');
20
+ }
21
+
22
+ el.style.userSelect = 'none';
23
+
24
+ this.svgRoot = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
25
+ this.svgRoot.style.width = '100%';
26
+ this.svgRoot.style.height = '100%';
27
+ this.svgRoot.style.position = 'absolute';
28
+ this.svgRoot.style.left = '0';
29
+ this.svgRoot.style.top = '0';
30
+ this.svgRoot.style.pointerEvents = 'none'; // usually good for overlays
31
+
32
+ el.appendChild(this.svgRoot);
33
+ }
34
+
35
+ draw(
36
+ shape: 'rect' | 'ellipse' | 'roundrect' | 'line',
37
+ fillColor: string,
38
+ lineColor: string,
39
+ lineWidth: string,
40
+ left: number,
41
+ top: number,
42
+ width: number,
43
+ height: number,
44
+ id?: string
45
+ ): SVGElement {
46
+ const svgNamespace = 'http://www.w3.org/2000/svg';
47
+ let svg: SVGElement;
48
+
49
+ if (shape === 'rect') {
50
+ svg = document.createElementNS(svgNamespace, 'rect');
51
+ svg.setAttributeNS(null, 'x', left + 'px');
52
+ svg.setAttributeNS(null, 'y', top + 'px');
53
+ svg.setAttributeNS(null, 'width', width + 'px');
54
+ svg.setAttributeNS(null, 'height', height + 'px');
55
+ } else if (shape === 'ellipse') {
56
+ svg = document.createElementNS(svgNamespace, 'ellipse');
57
+ svg.setAttributeNS(null, 'cx', left + width / 2 + 'px');
58
+ svg.setAttributeNS(null, 'cy', top + height / 2 + 'px');
59
+ svg.setAttributeNS(null, 'rx', width / 2 + 'px');
60
+ svg.setAttributeNS(null, 'ry', height / 2 + 'px');
61
+ } else if (shape === 'roundrect') {
62
+ svg = document.createElementNS(svgNamespace, 'rect');
63
+ svg.setAttributeNS(null, 'x', left + 'px');
64
+ svg.setAttributeNS(null, 'y', top + 'px');
65
+ svg.setAttributeNS(null, 'rx', '20px');
66
+ svg.setAttributeNS(null, 'ry', '20px');
67
+ svg.setAttributeNS(null, 'width', width + 'px');
68
+ svg.setAttributeNS(null, 'height', height + 'px');
69
+ } else if (shape === 'line') {
70
+ svg = document.createElementNS(svgNamespace, 'line');
71
+ svg.setAttributeNS(null, 'x1', left + 'px');
72
+ svg.setAttributeNS(null, 'y1', top + 'px');
73
+ svg.setAttributeNS(null, 'x2', width + 'px');
74
+ svg.setAttributeNS(null, 'y2', height + 'px');
75
+ } else {
76
+ throw new Error(`Unsupported shape: ${shape}`);
77
+ }
78
+
79
+ svg.style.position = 'absolute';
80
+ svg.setAttributeNS(null, 'fill', fillColor || 'none');
81
+ svg.setAttributeNS(null, 'stroke', lineColor || 'none');
82
+ svg.setAttributeNS(null, 'stroke-width', lineWidth || '1px');
83
+ svg.style.pointerEvents = 'auto'; // allow interaction with shape
84
+
85
+ if (id) {
86
+ svg.id = id;
87
+ this.elements.set(id, svg);
88
+ }
89
+ this.svgRoot.appendChild(svg);
90
+ return svg;
91
+ }
92
+
93
+ drawLine(
94
+ lineColor: string,
95
+ lineWidth: string,
96
+ left: number,
97
+ top: number,
98
+ width: number,
99
+ height: number,
100
+ id?: string
101
+ ): SVGElement {
102
+ return this.draw('line', '', lineColor, lineWidth, left, top, width, height, id);
103
+ }
104
+
105
+ resize(idOrObj: string | SVGElement, left: number, top: number, width: number, height: number): SVGElement | void {
106
+ const svg =
107
+ typeof idOrObj === 'string' ? this.elements.get(idOrObj) || (document.getElementById(idOrObj) as any) : idOrObj;
108
+ if (!svg) return;
109
+
110
+ if (svg.tagName === 'rect') {
111
+ svg.setAttributeNS(null, 'x', left + 'px');
112
+ svg.setAttributeNS(null, 'y', top + 'px');
113
+ svg.setAttributeNS(null, 'width', width + 'px');
114
+ svg.setAttributeNS(null, 'height', height + 'px');
115
+ } else if (svg.tagName === 'ellipse') {
116
+ svg.setAttributeNS(null, 'cx', left + width / 2 + 'px');
117
+ svg.setAttributeNS(null, 'cy', top + height / 2 + 'px');
118
+ svg.setAttributeNS(null, 'rx', width / 2 + 'px');
119
+ svg.setAttributeNS(null, 'ry', height / 2 + 'px');
120
+ } else if (svg.tagName === 'line') {
121
+ svg.setAttributeNS(null, 'x1', left + 'px');
122
+ svg.setAttributeNS(null, 'y1', top + 'px');
123
+ svg.setAttributeNS(null, 'x2', width + 'px');
124
+ svg.setAttributeNS(null, 'y2', height + 'px');
125
+ }
126
+ return svg;
127
+ }
128
+
129
+ remove(idOrObj: string | SVGElement): void {
130
+ const svg = typeof idOrObj === 'string' ? this.elements.get(idOrObj) || document.getElementById(idOrObj) : idOrObj;
131
+ if (svg && svg.parentNode) {
132
+ svg.parentNode.removeChild(svg);
133
+ if (typeof idOrObj === 'string') this.elements.delete(idOrObj);
134
+ }
135
+ }
136
+
137
+ setAttribute(shape: SVGElement, cmd: string, value: string): void {
138
+ if (shape != null) {
139
+ if (cmd === 'fillcolor') {
140
+ shape.setAttributeNS(null, 'fill', value || 'none');
141
+ } else if (cmd === 'linecolor') {
142
+ shape.setAttributeNS(null, 'stroke', value || 'none');
143
+ } else if (cmd === 'linewidth') {
144
+ shape.setAttributeNS(null, 'stroke-width', parseInt(value) + 'px');
145
+ }
146
+ }
147
+ }
148
+
149
+ getAttribute(shape: SVGElement, cmd: string): string {
150
+ let result = '';
151
+ if (shape != null) {
152
+ if (cmd === 'fillcolor') {
153
+ result = shape.getAttributeNS(null, 'fill') || '';
154
+ if (result === 'none') result = '';
155
+ } else if (cmd === 'linecolor') {
156
+ result = shape.getAttributeNS(null, 'stroke') || '';
157
+ if (result === 'none') result = '';
158
+ } else if (cmd === 'linewidth') {
159
+ result = shape.getAttributeNS(null, 'stroke') || '';
160
+ if (result === 'none') result = '';
161
+ else result = shape.getAttributeNS(null, 'stroke-width') || '';
162
+ }
163
+ }
164
+ return result;
165
+ }
166
+ }
@@ -5,13 +5,13 @@ export const youtubePlayerDemo: DemoStory<YouTubePlayerProps> = {
5
5
  id: 'youtubePlayerDemo',
6
6
  text: 'YouTube Player',
7
7
  args: {
8
- videoId: 'dQw4w9WgXcQ',
8
+ srcOrVideoId: 'dQw4w9WgXcQ',
9
9
  width: '100%',
10
10
  height: '400px',
11
11
  autoplay: false,
12
12
  },
13
13
  argTypes: {
14
- videoId: { control: 'text', description: 'YouTube Video ID' },
14
+ srcOrVideoId: { control: 'text', description: 'YouTube Video ID' },
15
15
  width: { control: 'text' },
16
16
  height: { control: 'text' },
17
17
  autoplay: { control: 'boolean' },
@@ -39,8 +39,8 @@ export const youtubePlayerDemo: DemoStory<YouTubePlayerProps> = {
39
39
  <section>
40
40
  <div class='section-title'>Custom Size</div>
41
41
  <div style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
42
- <YouTubePlayer videoId='jNQXAC9IVRw' width='300px' height='200px' />
43
- <YouTubePlayer videoId='M7lc1UVf-VE' width='400px' height='250px' />
42
+ <YouTubePlayer srcOrVideoId='jNQXAC9IVRw' width='300px' height='200px' />
43
+ <YouTubePlayer srcOrVideoId='M7lc1UVf-VE' width='400px' height='250px' />
44
44
  </div>
45
45
  </section>
46
46
  </div>
@@ -3,38 +3,114 @@ import { CssProps } from 'lupine.web';
3
3
  export type YouTubePlayerProps = {
4
4
  class?: string;
5
5
  style?: CssProps;
6
- videoId: string;
6
+ srcOrVideoId: string;
7
7
  width?: string | number;
8
8
  height?: string | number;
9
9
  autoplay?: boolean;
10
10
  allowFullScreen?: boolean;
11
+ controls?: boolean;
12
+ loop?: boolean;
13
+ muted?: boolean;
14
+ rel?: boolean;
15
+ modestbranding?: boolean;
11
16
  };
12
17
 
18
+ function parseYouTubeUrl(urlOrId: string) {
19
+ if (!urlOrId) return { videoId: '' };
20
+ if (/^[a-zA-Z0-9_-]{11}$/.test(urlOrId)) return { videoId: urlOrId };
21
+
22
+ let videoId = '';
23
+ let list = '';
24
+ let start = '';
25
+
26
+ try {
27
+ const urlObj = new URL(urlOrId);
28
+ if (urlObj.hostname.includes('youtube.com')) {
29
+ if (urlObj.pathname === '/watch') {
30
+ videoId = urlObj.searchParams.get('v') || '';
31
+ } else if (urlObj.pathname.startsWith('/embed/')) {
32
+ videoId = urlObj.pathname.split('/')[2] || '';
33
+ } else if (urlObj.pathname.startsWith('/v/')) {
34
+ videoId = urlObj.pathname.split('/')[2] || '';
35
+ }
36
+ list = urlObj.searchParams.get('list') || '';
37
+ start = urlObj.searchParams.get('t') || urlObj.searchParams.get('start') || '';
38
+ } else if (urlObj.hostname.includes('youtu.be')) {
39
+ videoId = urlObj.pathname.slice(1);
40
+ list = urlObj.searchParams.get('list') || '';
41
+ start = urlObj.searchParams.get('t') || urlObj.searchParams.get('start') || '';
42
+ }
43
+ } catch (e) {
44
+ const match = urlOrId.match(/(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([\w-]{11})/);
45
+ if (match) videoId = match[1];
46
+ }
47
+
48
+ if (start && isNaN(Number(start))) {
49
+ const timeMatch = start.match(/(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/);
50
+ if (timeMatch && timeMatch[0]) {
51
+ let seconds = 0;
52
+ if (timeMatch[1]) seconds += parseInt(timeMatch[1]) * 3600;
53
+ if (timeMatch[2]) seconds += parseInt(timeMatch[2]) * 60;
54
+ if (timeMatch[3]) seconds += parseInt(timeMatch[3]);
55
+ if (seconds > 0) start = seconds.toString();
56
+ }
57
+ }
58
+
59
+ return { videoId, list, start };
60
+ }
61
+
13
62
  export const YouTubePlayer = (props: YouTubePlayerProps) => {
14
63
  const {
15
- videoId,
64
+ srcOrVideoId,
16
65
  width = '100%',
17
66
  height = '100%',
18
67
  autoplay = false,
19
68
  allowFullScreen = true,
69
+ controls = true,
70
+ loop = false,
71
+ muted = false,
72
+ rel = false,
73
+ modestbranding = true,
20
74
  style,
21
75
  class: className,
22
76
  } = props;
23
77
 
24
- const src = `https://www.youtube.com/embed/${videoId}?autoplay=${autoplay ? 1 : 0}`;
78
+ const { videoId, list, start } = parseYouTubeUrl(srcOrVideoId);
79
+
80
+ if (typeof document === 'undefined') {
81
+ return <div class={className} css={{ position: 'relative', width, height, ...style }}></div>;
82
+ }
83
+
84
+ let src = '';
85
+ if (videoId || list) {
86
+ src = `https://www.youtube.com/embed/${videoId}?autoplay=${autoplay ? 1 : 0}`;
87
+ if (!controls) src += '&controls=0';
88
+ if (loop) src += `&loop=1&playlist=${videoId}`; // YouTube requires playlist=VIDEO_ID for loop to work
89
+ if (muted) src += '&mute=1';
90
+ if (!rel) src += '&rel=0';
91
+ if (modestbranding) src += '&modestbranding=1';
92
+ if (list) src += `&list=${list}`;
93
+ if (start) src += `&start=${start}`;
94
+ }
25
95
 
26
96
  return (
27
- <div class={className} css={{ position: 'relative', width, height, ...style }}>
28
- <iframe
29
- width='100%'
30
- height='100%'
31
- src={src}
32
- title='YouTube video player'
33
- frameBorder='0'
34
- allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
35
- allowFullScreen={allowFullScreen}
36
- style={{ border: 'none', display: 'block' }}
37
- ></iframe>
97
+ <div class={className} css={{ position: 'relative', width, height, display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', ...style }}>
98
+ {src ? (
99
+ <iframe
100
+ width='100%'
101
+ height='100%'
102
+ src={src}
103
+ title='YouTube video player'
104
+ frameBorder='0'
105
+ allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
106
+ allowFullScreen={allowFullScreen}
107
+ style={{ border: 'none', display: 'block', aspectRatio: '16 / 9', height: 'auto', maxHeight: '100%' }}
108
+ ></iframe>
109
+ ) : (
110
+ <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#f0f0f0', color: '#999', border: '1px solid #ddd' }}>
111
+ Invalid YouTube URL
112
+ </div>
113
+ )}
38
114
  </div>
39
115
  );
40
116
  };
@@ -10,6 +10,7 @@ import {
10
10
  import { ActionSheetTimePicker } from './action-sheet-time';
11
11
  import { ActionSheetDatePicker } from './action-sheet-date';
12
12
  import { ActionSheetColorPicker } from './action-sheet-color';
13
+ import { ActionSheetThemePicker } from './action-sheet-theme';
13
14
  import { Button, ButtonSize } from './button';
14
15
 
15
16
  export const actionSheetDemo: DemoStory<any> = {
@@ -180,6 +181,17 @@ export const actionSheetDemo: DemoStory<any> = {
180
181
  if (result) console.log('Color selected:', result);
181
182
  }}
182
183
  />
184
+ <Button
185
+ text='Show Theme Picker'
186
+ size={ButtonSize.Medium}
187
+ onClick={async () => {
188
+ const result = await ActionSheetThemePicker({
189
+ title: 'Pick a theme variable',
190
+ value: 'var(--primary-color)',
191
+ });
192
+ if (result) console.log('Theme variable selected:', result);
193
+ }}
194
+ />
183
195
  </div>
184
196
  );
185
197
  },