Skip to content

Commit cb673f0

Browse files
committed
restructured the example
1 parent 6c4cfe6 commit cb673f0

File tree

12 files changed

+885
-742
lines changed

12 files changed

+885
-742
lines changed

example/src/App.tsx

Lines changed: 81 additions & 740 deletions
Large diffs are not rendered by default.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React from 'react'
2+
import ColorPickerInput from '../ui/ColorPickerInput'
3+
import type { WidgetConfig } from '../../types'
4+
5+
interface AppearanceSectionProps {
6+
config: WidgetConfig
7+
updateConfig: (key: keyof WidgetConfig, value: any) => void
8+
}
9+
10+
const AppearanceSection: React.FC<AppearanceSectionProps> = ({ config, updateConfig }) => (
11+
<div className="bg-gray-50 rounded-lg p-6">
12+
<div className="flex items-center justify-between mb-4">
13+
<h2 className="text-lg font-medium text-gray-900">
14+
Appearance
15+
</h2>
16+
<svg className="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
17+
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
18+
</svg>
19+
</div>
20+
<p className="text-sm mb-6 text-gray-600">
21+
Customize how the widget looks.
22+
</p>
23+
24+
{/* Theme */}
25+
<div className="mb-6">
26+
<label className="block text-sm font-medium mb-3 text-gray-700">
27+
Theme
28+
</label>
29+
<select
30+
value={config.theme}
31+
onChange={(e) => {
32+
const newTheme = e.target.value as 'light' | 'dark'
33+
updateConfig('theme', newTheme)
34+
// Automatically adjust base color based on theme
35+
if (newTheme === 'dark') {
36+
updateConfig('baseColor', '#000000')
37+
} else {
38+
updateConfig('baseColor', '#ffffff')
39+
}
40+
}}
41+
className="w-full p-2 rounded-md border bg-white border-gray-300 text-gray-900"
42+
>
43+
<option value="light">Light</option>
44+
<option value="dark">Dark</option>
45+
</select>
46+
<p className="text-xs mt-1 text-gray-500">
47+
Switching themes will automatically adjust base and button colors
48+
</p>
49+
</div>
50+
51+
{/* Color Inputs */}
52+
<div className="grid grid-cols-2 gap-4">
53+
<div>
54+
<ColorPickerInput
55+
label="Base Color"
56+
value={config.baseColor}
57+
onChange={(color) => updateConfig('baseColor', color)}
58+
/>
59+
</div>
60+
61+
<div>
62+
<ColorPickerInput
63+
label="Accent Color"
64+
value={config.accentColor}
65+
onChange={(color) => updateConfig('accentColor', color)}
66+
/>
67+
</div>
68+
69+
<div>
70+
<ColorPickerInput
71+
label="Button Base Color"
72+
value={config.buttonBaseColor}
73+
onChange={(color) => updateConfig('buttonBaseColor', color)}
74+
/>
75+
</div>
76+
77+
<div>
78+
<ColorPickerInput
79+
label="Button Accent Color"
80+
value={config.buttonAccentColor}
81+
onChange={(color) => updateConfig('buttonAccentColor', color)}
82+
/>
83+
</div>
84+
</div>
85+
</div>
86+
)
87+
88+
export default AppearanceSection
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React from 'react'
2+
import type { WidgetConfig } from '../../types'
3+
4+
interface LayoutSectionProps {
5+
config: WidgetConfig
6+
updateConfig: (key: keyof WidgetConfig, value: any) => void
7+
}
8+
9+
const LayoutSection: React.FC<LayoutSectionProps> = ({ config, updateConfig }) => (
10+
<div className="bg-gray-50 rounded-lg p-6">
11+
<div className="flex items-center justify-between mb-4">
12+
<h2 className="text-lg font-medium text-gray-900">
13+
Layout
14+
</h2>
15+
</div>
16+
17+
{/* Position */}
18+
<div className="mb-6">
19+
<label className="block text-sm font-medium mb-3 text-gray-700">
20+
Position
21+
</label>
22+
<select
23+
value={config.position}
24+
onChange={(e) => updateConfig('position', e.target.value as any)}
25+
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500 transition-colors bg-white text-gray-900"
26+
>
27+
{(['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const).map((position) => (
28+
<option key={position} value={position}>
29+
{position.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}
30+
</option>
31+
))}
32+
</select>
33+
</div>
34+
35+
{/* Radius */}
36+
<div className="mb-6">
37+
<label className="block text-sm font-medium mb-3 text-gray-700">
38+
Radius
39+
</label>
40+
<div className="flex gap-3">
41+
{(['none', 'small', 'medium', 'large'] as const).map((radius) => (
42+
<div
43+
key={radius}
44+
className={`flex-1 p-4 rounded-lg border cursor-pointer transition-all ${
45+
config.radius === radius
46+
? 'border-gray-700 bg-gray-50'
47+
: 'border-gray-300 hover:border-gray-400 bg-white'
48+
}`}
49+
onClick={() => updateConfig('radius', radius)}
50+
>
51+
<div className="flex items-center gap-3">
52+
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
53+
config.radius === radius
54+
? 'border-teal-500'
55+
: 'border-gray-400'
56+
}`}>
57+
{config.radius === radius && (
58+
<div className="w-3 h-3 bg-teal-500 rounded-full"></div>
59+
)}
60+
</div>
61+
<span className="text-sm capitalize text-gray-900">
62+
{radius}
63+
</span>
64+
</div>
65+
</div>
66+
))}
67+
</div>
68+
</div>
69+
70+
{/* Size */}
71+
<div className="mb-6">
72+
<label className="block text-sm font-medium mb-3 text-gray-700">
73+
Size
74+
</label>
75+
{config.mode !== 'voice' && (
76+
<p className="text-xs text-gray-500 mb-2">
77+
Note: "Tiny" size only works in Voice mode. Chat and Hybrid modes will use Compact size instead.
78+
</p>
79+
)}
80+
<div className="flex gap-3">
81+
{(['tiny', 'compact', 'full'] as const).map((size) => (
82+
<div
83+
key={size}
84+
className={`flex-1 p-4 rounded-lg border cursor-pointer transition-all ${
85+
config.size === size
86+
? 'border-gray-700 bg-gray-50'
87+
: 'border-gray-300 hover:border-gray-400 bg-white'
88+
}`}
89+
onClick={() => updateConfig('size', size)}
90+
>
91+
<div className="flex items-center gap-3">
92+
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
93+
config.size === size
94+
? 'border-teal-500'
95+
: 'border-gray-400'
96+
}`}>
97+
{config.size === size && (
98+
<div className="w-3 h-3 bg-teal-500 rounded-full"></div>
99+
)}
100+
</div>
101+
<span className="text-sm capitalize text-gray-900">
102+
{size}
103+
</span>
104+
</div>
105+
</div>
106+
))}
107+
</div>
108+
{config.size === 'tiny' && config.mode === 'voice' && (
109+
<p className="text-xs text-teal-600 mt-2">
110+
In tiny voice mode, clicking the button directly starts/stops the call. The widget shows a glowing indicator during calls.
111+
</p>
112+
)}
113+
</div>
114+
</div>
115+
)
116+
117+
export default LayoutSection
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React from 'react'
2+
import { ArrowsClockwiseIcon } from '@phosphor-icons/react'
3+
import type { WidgetConfig } from '../../types'
4+
5+
interface LegalConsentSectionProps {
6+
config: WidgetConfig
7+
updateConfig: (key: keyof WidgetConfig, value: any) => void
8+
}
9+
10+
const LegalConsentSection: React.FC<LegalConsentSectionProps> = ({ config, updateConfig }) => (
11+
<div className="bg-gray-50 rounded-lg p-6">
12+
<div className="flex items-center justify-between mb-4">
13+
<h2 className="text-lg font-medium text-gray-900">
14+
Legal & Consent
15+
</h2>
16+
<label className="relative inline-flex items-center cursor-pointer">
17+
<input
18+
type="checkbox"
19+
checked={config.requireConsent}
20+
onChange={(e) => updateConfig('requireConsent', e.target.checked)}
21+
className="sr-only peer"
22+
/>
23+
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-teal-600"></div>
24+
</label>
25+
</div>
26+
<p className="text-sm mb-6 text-gray-600">
27+
Require terms and Conditions
28+
</p>
29+
30+
{config.requireConsent && (
31+
<div className="space-y-4">
32+
<div>
33+
<label className="block text-sm font-medium mb-2 text-gray-700">
34+
Terms Content
35+
</label>
36+
<div className="border rounded-md p-3 border-gray-300 bg-white">
37+
<div className="flex items-center space-x-2 mb-3">
38+
<span className="text-sm font-medium text-gray-700">
39+
#### Terms and conditions
40+
</span>
41+
</div>
42+
<textarea
43+
value={config.termsContent}
44+
onChange={(e) => updateConfig('termsContent', e.target.value)}
45+
rows={4}
46+
className="w-full p-2 rounded-md border resize-none bg-gray-50 border-gray-200 text-gray-900"
47+
/>
48+
<p className="text-xs mt-2 text-gray-500">
49+
Rich text supported
50+
</p>
51+
</div>
52+
</div>
53+
54+
<div>
55+
<label className="block text-sm font-medium mb-2 text-gray-700">
56+
Local Storage Key
57+
<svg className="w-3 h-3 inline ml-1 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
58+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
59+
</svg>
60+
</label>
61+
<div className="flex items-center space-x-2">
62+
<input
63+
type="text"
64+
value={config.localStorageKey}
65+
onChange={(e) => updateConfig('localStorageKey', e.target.value)}
66+
className="flex-1 p-2 rounded-md border font-mono text-sm bg-white border-gray-300 text-gray-900"
67+
/>
68+
<button
69+
onClick={() => {
70+
localStorage.removeItem(config.localStorageKey)
71+
// Force a page refresh to reset the widget's consent state
72+
window.location.reload()
73+
}}
74+
className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-md transition-colors"
75+
title="Clear stored consent"
76+
>
77+
<ArrowsClockwiseIcon size={16} weight="bold" />
78+
</button>
79+
</div>
80+
<p className="text-xs mt-1 text-gray-500">
81+
Key used to store consent in browser. Click reset icon to clear.
82+
</p>
83+
</div>
84+
</div>
85+
)}
86+
</div>
87+
)
88+
89+
export default LegalConsentSection

0 commit comments

Comments
 (0)