@thewhateverapp/tile-sdk 0.13.31 → 0.13.33
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/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/spec/host/OverlayHost.d.ts +68 -0
- package/dist/spec/host/OverlayHost.d.ts.map +1 -0
- package/dist/spec/host/OverlayHost.js +143 -0
- package/dist/spec/host/index.d.ts +7 -0
- package/dist/spec/host/index.d.ts.map +1 -0
- package/dist/spec/host/index.js +6 -0
- package/dist/spec/index.d.ts +29 -0
- package/dist/spec/index.d.ts.map +1 -0
- package/dist/spec/index.js +81 -0
- package/dist/spec/registry/ComponentRegistry.d.ts +208 -0
- package/dist/spec/registry/ComponentRegistry.d.ts.map +1 -0
- package/dist/spec/registry/ComponentRegistry.js +227 -0
- package/dist/spec/registry/composites/BottomSheet.d.ts +33 -0
- package/dist/spec/registry/composites/BottomSheet.d.ts.map +1 -0
- package/dist/spec/registry/composites/BottomSheet.js +98 -0
- package/dist/spec/registry/composites/CountdownCTA.d.ts +35 -0
- package/dist/spec/registry/composites/CountdownCTA.d.ts.map +1 -0
- package/dist/spec/registry/composites/CountdownCTA.js +91 -0
- package/dist/spec/registry/composites/Poll.d.ts +39 -0
- package/dist/spec/registry/composites/Poll.d.ts.map +1 -0
- package/dist/spec/registry/composites/Poll.js +76 -0
- package/dist/spec/registry/composites/Prediction.d.ts +37 -0
- package/dist/spec/registry/composites/Prediction.d.ts.map +1 -0
- package/dist/spec/registry/composites/Prediction.js +116 -0
- package/dist/spec/registry/composites/index.d.ts +33 -0
- package/dist/spec/registry/composites/index.d.ts.map +1 -0
- package/dist/spec/registry/composites/index.js +36 -0
- package/dist/spec/registry/index.d.ts +15 -0
- package/dist/spec/registry/index.d.ts.map +1 -0
- package/dist/spec/registry/index.js +24 -0
- package/dist/spec/registry/primitives/Button.d.ts +30 -0
- package/dist/spec/registry/primitives/Button.d.ts.map +1 -0
- package/dist/spec/registry/primitives/Button.js +62 -0
- package/dist/spec/registry/primitives/Divider.d.ts +22 -0
- package/dist/spec/registry/primitives/Divider.d.ts.map +1 -0
- package/dist/spec/registry/primitives/Divider.js +56 -0
- package/dist/spec/registry/primitives/Image.d.ts +27 -0
- package/dist/spec/registry/primitives/Image.d.ts.map +1 -0
- package/dist/spec/registry/primitives/Image.js +36 -0
- package/dist/spec/registry/primitives/ProgressBar.d.ts +28 -0
- package/dist/spec/registry/primitives/ProgressBar.d.ts.map +1 -0
- package/dist/spec/registry/primitives/ProgressBar.js +50 -0
- package/dist/spec/registry/primitives/Row.d.ts +26 -0
- package/dist/spec/registry/primitives/Row.d.ts.map +1 -0
- package/dist/spec/registry/primitives/Row.js +50 -0
- package/dist/spec/registry/primitives/Spacer.d.ts +18 -0
- package/dist/spec/registry/primitives/Spacer.d.ts.map +1 -0
- package/dist/spec/registry/primitives/Spacer.js +25 -0
- package/dist/spec/registry/primitives/Stack.d.ts +22 -0
- package/dist/spec/registry/primitives/Stack.d.ts.map +1 -0
- package/dist/spec/registry/primitives/Stack.js +41 -0
- package/dist/spec/registry/primitives/Text.d.ts +26 -0
- package/dist/spec/registry/primitives/Text.d.ts.map +1 -0
- package/dist/spec/registry/primitives/Text.js +33 -0
- package/dist/spec/registry/primitives/index.d.ts +45 -0
- package/dist/spec/registry/primitives/index.d.ts.map +1 -0
- package/dist/spec/registry/primitives/index.js +55 -0
- package/dist/spec/renderer/BindingResolver.d.ts +35 -0
- package/dist/spec/renderer/BindingResolver.d.ts.map +1 -0
- package/dist/spec/renderer/BindingResolver.js +131 -0
- package/dist/spec/renderer/CaptionTrack.d.ts +22 -0
- package/dist/spec/renderer/CaptionTrack.d.ts.map +1 -0
- package/dist/spec/renderer/CaptionTrack.js +83 -0
- package/dist/spec/renderer/LayoutRenderer.d.ts +24 -0
- package/dist/spec/renderer/LayoutRenderer.d.ts.map +1 -0
- package/dist/spec/renderer/LayoutRenderer.js +66 -0
- package/dist/spec/renderer/OverlayCue.d.ts +20 -0
- package/dist/spec/renderer/OverlayCue.d.ts.map +1 -0
- package/dist/spec/renderer/OverlayCue.js +161 -0
- package/dist/spec/renderer/index.d.ts +10 -0
- package/dist/spec/renderer/index.d.ts.map +1 -0
- package/dist/spec/renderer/index.js +13 -0
- package/dist/spec/runtime/ActionRouter.d.ts +33 -0
- package/dist/spec/runtime/ActionRouter.d.ts.map +1 -0
- package/dist/spec/runtime/ActionRouter.js +84 -0
- package/dist/spec/runtime/OverlayRuntime.d.ts +84 -0
- package/dist/spec/runtime/OverlayRuntime.d.ts.map +1 -0
- package/dist/spec/runtime/OverlayRuntime.js +216 -0
- package/dist/spec/runtime/StateManager.d.ts +31 -0
- package/dist/spec/runtime/StateManager.d.ts.map +1 -0
- package/dist/spec/runtime/StateManager.js +60 -0
- package/dist/spec/runtime/TimeSync.d.ts +47 -0
- package/dist/spec/runtime/TimeSync.d.ts.map +1 -0
- package/dist/spec/runtime/TimeSync.js +140 -0
- package/dist/spec/runtime/index.d.ts +10 -0
- package/dist/spec/runtime/index.d.ts.map +1 -0
- package/dist/spec/runtime/index.js +13 -0
- package/dist/spec/schema.d.ts +889 -0
- package/dist/spec/schema.d.ts.map +1 -0
- package/dist/spec/schema.js +284 -0
- package/dist/spec/theme/ThemeProvider.d.ts +151 -0
- package/dist/spec/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/spec/theme/ThemeProvider.js +227 -0
- package/dist/spec/theme/index.d.ts +7 -0
- package/dist/spec/theme/index.d.ts.map +1 -0
- package/dist/spec/theme/index.js +12 -0
- package/dist/spec/types.d.ts +322 -0
- package/dist/spec/types.d.ts.map +1 -0
- package/dist/spec/types.js +36 -0
- package/package.json +8 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Registry
|
|
3
|
+
*
|
|
4
|
+
* Maps component type IDs (e.g., "ui.text", "ui.button") to React components.
|
|
5
|
+
* The spec uses string IDs; the registry resolves them to actual components.
|
|
6
|
+
*/
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Global Registry
|
|
9
|
+
// =============================================================================
|
|
10
|
+
const registry = new Map();
|
|
11
|
+
/**
|
|
12
|
+
* Register a component with the registry.
|
|
13
|
+
* Components should extend RegistryComponentProps but can have additional required props.
|
|
14
|
+
*/
|
|
15
|
+
export function registerComponent(type,
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
component, options) {
|
|
18
|
+
registry.set(type, {
|
|
19
|
+
component,
|
|
20
|
+
defaults: options?.defaults,
|
|
21
|
+
transformProps: options?.transformProps,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get a component from the registry
|
|
26
|
+
*/
|
|
27
|
+
export function getComponent(type) {
|
|
28
|
+
return registry.get(type);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if a component type is registered
|
|
32
|
+
*/
|
|
33
|
+
export function hasComponent(type) {
|
|
34
|
+
return registry.has(type);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get all registered component types
|
|
38
|
+
*/
|
|
39
|
+
export function getRegisteredTypes() {
|
|
40
|
+
return Array.from(registry.keys());
|
|
41
|
+
}
|
|
42
|
+
/** Alias for getRegisteredTypes */
|
|
43
|
+
export const getAllComponentIds = getRegisteredTypes;
|
|
44
|
+
/**
|
|
45
|
+
* Clear the registry (useful for testing)
|
|
46
|
+
*/
|
|
47
|
+
export function clearRegistry() {
|
|
48
|
+
registry.clear();
|
|
49
|
+
}
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Component Registry Class (for scoped registries)
|
|
52
|
+
// =============================================================================
|
|
53
|
+
export class ComponentRegistry {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.components = new Map();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Register a component
|
|
59
|
+
*/
|
|
60
|
+
register(type, component, options) {
|
|
61
|
+
this.components.set(type, {
|
|
62
|
+
component,
|
|
63
|
+
defaults: options?.defaults,
|
|
64
|
+
transformProps: options?.transformProps,
|
|
65
|
+
});
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get a component definition
|
|
70
|
+
*/
|
|
71
|
+
get(type) {
|
|
72
|
+
return this.components.get(type);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if type is registered
|
|
76
|
+
*/
|
|
77
|
+
has(type) {
|
|
78
|
+
return this.components.has(type);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get all registered types
|
|
82
|
+
*/
|
|
83
|
+
types() {
|
|
84
|
+
return Array.from(this.components.keys());
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Merge another registry into this one
|
|
88
|
+
*/
|
|
89
|
+
merge(other) {
|
|
90
|
+
for (const [type, def] of other.components) {
|
|
91
|
+
this.components.set(type, def);
|
|
92
|
+
}
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Create a child registry that inherits from this one
|
|
97
|
+
*/
|
|
98
|
+
extend() {
|
|
99
|
+
const child = new ComponentRegistry();
|
|
100
|
+
child.merge(this);
|
|
101
|
+
return child;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// =============================================================================
|
|
105
|
+
// Default Registry Instance
|
|
106
|
+
// =============================================================================
|
|
107
|
+
export const defaultRegistry = new ComponentRegistry();
|
|
108
|
+
/** Alias for defaultRegistry (for compatibility) */
|
|
109
|
+
export const globalRegistry = defaultRegistry;
|
|
110
|
+
// =============================================================================
|
|
111
|
+
// Fallback Component
|
|
112
|
+
// =============================================================================
|
|
113
|
+
/**
|
|
114
|
+
* Fallback component for unknown types.
|
|
115
|
+
* This is defined in the React component files that use it.
|
|
116
|
+
*/
|
|
117
|
+
export function createUnknownComponentMessage(type) {
|
|
118
|
+
return `Unknown component: ${type}`;
|
|
119
|
+
}
|
|
120
|
+
// =============================================================================
|
|
121
|
+
// Design Token Utilities
|
|
122
|
+
// =============================================================================
|
|
123
|
+
/**
|
|
124
|
+
* Size token to Tailwind class mapping
|
|
125
|
+
*/
|
|
126
|
+
export const sizeClasses = {
|
|
127
|
+
xs: 'text-xs',
|
|
128
|
+
sm: 'text-sm',
|
|
129
|
+
md: 'text-base',
|
|
130
|
+
lg: 'text-lg',
|
|
131
|
+
xl: 'text-xl',
|
|
132
|
+
'2xl': 'text-2xl',
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Weight token to Tailwind class mapping
|
|
136
|
+
*/
|
|
137
|
+
export const weightClasses = {
|
|
138
|
+
normal: 'font-normal',
|
|
139
|
+
medium: 'font-medium',
|
|
140
|
+
semibold: 'font-semibold',
|
|
141
|
+
bold: 'font-bold',
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Color token to Tailwind class mapping
|
|
145
|
+
*/
|
|
146
|
+
export const colorClasses = {
|
|
147
|
+
primary: 'text-purple-500',
|
|
148
|
+
secondary: 'text-gray-500',
|
|
149
|
+
success: 'text-green-500',
|
|
150
|
+
warning: 'text-yellow-500',
|
|
151
|
+
error: 'text-red-500',
|
|
152
|
+
muted: 'text-gray-400',
|
|
153
|
+
white: 'text-white',
|
|
154
|
+
black: 'text-black',
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Background color token to Tailwind class mapping
|
|
158
|
+
*/
|
|
159
|
+
export const bgColorClasses = {
|
|
160
|
+
primary: 'bg-purple-500',
|
|
161
|
+
secondary: 'bg-gray-500',
|
|
162
|
+
success: 'bg-green-500',
|
|
163
|
+
warning: 'bg-yellow-500',
|
|
164
|
+
error: 'bg-red-500',
|
|
165
|
+
muted: 'bg-gray-400',
|
|
166
|
+
white: 'bg-white',
|
|
167
|
+
black: 'bg-black',
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Variant token to button style mapping
|
|
171
|
+
*/
|
|
172
|
+
export const variantStyles = {
|
|
173
|
+
solid: {
|
|
174
|
+
base: 'bg-purple-500 text-white hover:bg-purple-600',
|
|
175
|
+
disabled: 'bg-gray-300 text-gray-500',
|
|
176
|
+
},
|
|
177
|
+
outline: {
|
|
178
|
+
base: 'border-2 border-purple-500 text-purple-500 hover:bg-purple-50',
|
|
179
|
+
disabled: 'border-gray-300 text-gray-400',
|
|
180
|
+
},
|
|
181
|
+
ghost: {
|
|
182
|
+
base: 'text-purple-500 hover:bg-purple-50',
|
|
183
|
+
disabled: 'text-gray-400',
|
|
184
|
+
},
|
|
185
|
+
link: {
|
|
186
|
+
base: 'text-purple-500 underline hover:text-purple-600',
|
|
187
|
+
disabled: 'text-gray-400',
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Gap token to Tailwind class mapping
|
|
192
|
+
*/
|
|
193
|
+
export const gapClasses = {
|
|
194
|
+
xs: 'gap-1',
|
|
195
|
+
sm: 'gap-2',
|
|
196
|
+
md: 'gap-4',
|
|
197
|
+
lg: 'gap-6',
|
|
198
|
+
xl: 'gap-8',
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Padding token to Tailwind class mapping
|
|
202
|
+
*/
|
|
203
|
+
export const paddingClasses = {
|
|
204
|
+
xs: 'p-1',
|
|
205
|
+
sm: 'p-2',
|
|
206
|
+
md: 'p-4',
|
|
207
|
+
lg: 'p-6',
|
|
208
|
+
xl: 'p-8',
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Align token to Tailwind class mapping
|
|
212
|
+
*/
|
|
213
|
+
export const alignClasses = {
|
|
214
|
+
left: 'items-start',
|
|
215
|
+
center: 'items-center',
|
|
216
|
+
right: 'items-end',
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Justify token to Tailwind class mapping
|
|
220
|
+
*/
|
|
221
|
+
export const justifyClasses = {
|
|
222
|
+
start: 'justify-start',
|
|
223
|
+
center: 'justify-center',
|
|
224
|
+
end: 'justify-end',
|
|
225
|
+
between: 'justify-between',
|
|
226
|
+
around: 'justify-around',
|
|
227
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BottomSheet Component
|
|
3
|
+
*
|
|
4
|
+
* Slide-up panel with drag-to-dismiss support.
|
|
5
|
+
*/
|
|
6
|
+
import type { RegistryComponentProps } from '../ComponentRegistry';
|
|
7
|
+
export interface BottomSheetProps extends RegistryComponentProps {
|
|
8
|
+
/** Whether the sheet is open */
|
|
9
|
+
open?: boolean;
|
|
10
|
+
/** Callback when sheet should close */
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
/** Header title */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** Show close button */
|
|
15
|
+
showCloseButton?: boolean;
|
|
16
|
+
/** Show drag handle */
|
|
17
|
+
showDragHandle?: boolean;
|
|
18
|
+
/** Enable drag to dismiss */
|
|
19
|
+
dragToDismiss?: boolean;
|
|
20
|
+
/** Snap points (percentage of screen height) */
|
|
21
|
+
snapPoints?: number[];
|
|
22
|
+
/** Initial snap point index */
|
|
23
|
+
initialSnap?: number;
|
|
24
|
+
/** Backdrop opacity */
|
|
25
|
+
backdropOpacity?: number;
|
|
26
|
+
/** Click backdrop to close */
|
|
27
|
+
closeOnBackdropClick?: boolean;
|
|
28
|
+
/** Additional CSS classes */
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function BottomSheet({ open, onClose, title, showCloseButton, showDragHandle, dragToDismiss, snapPoints, initialSnap, backdropOpacity, closeOnBackdropClick, className, children, }: BottomSheetProps): JSX.Element | null;
|
|
32
|
+
export default BottomSheet;
|
|
33
|
+
//# sourceMappingURL=BottomSheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../../../src/spec/registry/composites/BottomSheet.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,WAAW,gBAAiB,SAAQ,sBAAsB;IAC9D,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,wBAAwB;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,uBAAuB;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB,6BAA6B;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,8BAA8B;IAC9B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD,wBAAgB,WAAW,CAAC,EAC1B,IAAW,EACX,OAAO,EACP,KAAK,EACL,eAAsB,EACtB,cAAqB,EACrB,aAAoB,EACpB,UAAkB,EAClB,WAAe,EACf,eAAqB,EACrB,oBAA2B,EAC3B,SAAc,EACd,QAAQ,GACT,EAAE,gBAAgB,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CA6JvC;AAED,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BottomSheet Component
|
|
3
|
+
*
|
|
4
|
+
* Slide-up panel with drag-to-dismiss support.
|
|
5
|
+
*/
|
|
6
|
+
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
7
|
+
const DRAG_THRESHOLD = 100; // px to trigger dismiss
|
|
8
|
+
const VELOCITY_THRESHOLD = 500; // px/s to trigger dismiss
|
|
9
|
+
export function BottomSheet({ open = true, onClose, title, showCloseButton = true, showDragHandle = true, dragToDismiss = true, snapPoints = [0.5], initialSnap = 0, backdropOpacity = 0.5, closeOnBackdropClick = true, className = '', children, }) {
|
|
10
|
+
const [isVisible, setIsVisible] = useState(open);
|
|
11
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
12
|
+
const [dragOffset, setDragOffset] = useState(0);
|
|
13
|
+
const [currentSnap, setCurrentSnap] = useState(initialSnap);
|
|
14
|
+
const sheetRef = useRef(null);
|
|
15
|
+
const dragStartRef = useRef(null);
|
|
16
|
+
// Handle open/close
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (open) {
|
|
19
|
+
setIsVisible(true);
|
|
20
|
+
setIsAnimating(true);
|
|
21
|
+
setTimeout(() => setIsAnimating(false), 300);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
setIsAnimating(true);
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
setIsVisible(false);
|
|
27
|
+
setIsAnimating(false);
|
|
28
|
+
}, 300);
|
|
29
|
+
}
|
|
30
|
+
}, [open]);
|
|
31
|
+
// Get height for current snap point
|
|
32
|
+
const snapHeight = snapPoints[currentSnap] ?? 0.5;
|
|
33
|
+
// Handle backdrop click
|
|
34
|
+
const handleBackdropClick = useCallback(() => {
|
|
35
|
+
if (closeOnBackdropClick && onClose) {
|
|
36
|
+
onClose();
|
|
37
|
+
}
|
|
38
|
+
}, [closeOnBackdropClick, onClose]);
|
|
39
|
+
// Handle close button
|
|
40
|
+
const handleClose = useCallback(() => {
|
|
41
|
+
onClose?.();
|
|
42
|
+
}, [onClose]);
|
|
43
|
+
// Drag handlers
|
|
44
|
+
const handleDragStart = useCallback((e) => {
|
|
45
|
+
if (!dragToDismiss)
|
|
46
|
+
return;
|
|
47
|
+
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
48
|
+
dragStartRef.current = { y: clientY, time: Date.now() };
|
|
49
|
+
}, [dragToDismiss]);
|
|
50
|
+
const handleDragMove = useCallback((e) => {
|
|
51
|
+
if (!dragStartRef.current || !dragToDismiss)
|
|
52
|
+
return;
|
|
53
|
+
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
54
|
+
const delta = clientY - dragStartRef.current.y;
|
|
55
|
+
// Only allow dragging down
|
|
56
|
+
if (delta > 0) {
|
|
57
|
+
setDragOffset(delta);
|
|
58
|
+
}
|
|
59
|
+
}, [dragToDismiss]);
|
|
60
|
+
const handleDragEnd = useCallback(() => {
|
|
61
|
+
if (!dragStartRef.current || !dragToDismiss)
|
|
62
|
+
return;
|
|
63
|
+
const elapsed = Date.now() - dragStartRef.current.time;
|
|
64
|
+
const velocity = (dragOffset / elapsed) * 1000;
|
|
65
|
+
// Dismiss if dragged far enough or fast enough
|
|
66
|
+
if (dragOffset > DRAG_THRESHOLD || velocity > VELOCITY_THRESHOLD) {
|
|
67
|
+
onClose?.();
|
|
68
|
+
}
|
|
69
|
+
setDragOffset(0);
|
|
70
|
+
dragStartRef.current = null;
|
|
71
|
+
}, [dragToDismiss, dragOffset, onClose]);
|
|
72
|
+
if (!isVisible) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return (React.createElement("div", { className: "bottom-sheet-container fixed inset-0 z-50" },
|
|
76
|
+
React.createElement("div", { className: `
|
|
77
|
+
absolute inset-0 bg-black transition-opacity duration-300
|
|
78
|
+
${open && !isAnimating ? '' : 'opacity-0'}
|
|
79
|
+
`, style: { opacity: open && !isAnimating ? backdropOpacity : 0 }, onClick: handleBackdropClick }),
|
|
80
|
+
React.createElement("div", { ref: sheetRef, className: `
|
|
81
|
+
absolute bottom-0 left-0 right-0
|
|
82
|
+
bg-gray-800 rounded-t-2xl
|
|
83
|
+
transition-transform duration-300 ease-out
|
|
84
|
+
${className}
|
|
85
|
+
`, style: {
|
|
86
|
+
height: `${snapHeight * 100}%`,
|
|
87
|
+
transform: `translateY(${open && !isAnimating ? dragOffset : '100%'}px)`,
|
|
88
|
+
}, onTouchStart: handleDragStart, onTouchMove: handleDragMove, onTouchEnd: handleDragEnd, onMouseDown: handleDragStart, onMouseMove: handleDragMove, onMouseUp: handleDragEnd, onMouseLeave: handleDragEnd },
|
|
89
|
+
showDragHandle && (React.createElement("div", { className: "flex justify-center pt-3 pb-2" },
|
|
90
|
+
React.createElement("div", { className: "w-10 h-1 bg-gray-600 rounded-full" }))),
|
|
91
|
+
(title || showCloseButton) && (React.createElement("div", { className: "flex items-center justify-between px-4 py-2 border-b border-gray-700" },
|
|
92
|
+
React.createElement("h3", { className: "text-white font-semibold text-lg" }, title),
|
|
93
|
+
showCloseButton && (React.createElement("button", { onClick: handleClose, className: "p-2 text-gray-400 hover:text-white transition-colors", "aria-label": "Close" },
|
|
94
|
+
React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2" },
|
|
95
|
+
React.createElement("path", { d: "M15 5L5 15M5 5l10 10" })))))),
|
|
96
|
+
React.createElement("div", { className: "overflow-auto", style: { maxHeight: 'calc(100% - 60px)' } }, children))));
|
|
97
|
+
}
|
|
98
|
+
export default BottomSheet;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CountdownCTA Component
|
|
3
|
+
*
|
|
4
|
+
* Urgency countdown with action button.
|
|
5
|
+
*/
|
|
6
|
+
import type { RegistryComponentProps } from '../ComponentRegistry';
|
|
7
|
+
import type { Color, Variant } from '../../types';
|
|
8
|
+
export interface CountdownCTAProps extends RegistryComponentProps {
|
|
9
|
+
/** Main headline text */
|
|
10
|
+
headline?: string;
|
|
11
|
+
/** Subtext / description */
|
|
12
|
+
subtext?: string;
|
|
13
|
+
/** Countdown end time (ISO string or ms timestamp) */
|
|
14
|
+
endsAt: string | number;
|
|
15
|
+
/** Button label */
|
|
16
|
+
buttonLabel: string;
|
|
17
|
+
/** Button action callback */
|
|
18
|
+
onPress?: () => void;
|
|
19
|
+
/** Expired state label */
|
|
20
|
+
expiredLabel?: string;
|
|
21
|
+
/** Show urgency styling when time is low */
|
|
22
|
+
urgentThresholdMs?: number;
|
|
23
|
+
/** Color theme */
|
|
24
|
+
color?: Color;
|
|
25
|
+
/** Button variant */
|
|
26
|
+
variant?: Variant;
|
|
27
|
+
/** Compact mode */
|
|
28
|
+
compact?: boolean;
|
|
29
|
+
/** Additional CSS classes */
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function CountdownCTA({ headline, subtext, endsAt, buttonLabel, onPress, expiredLabel, urgentThresholdMs, // 1 minute
|
|
33
|
+
color, variant, compact, className, }: CountdownCTAProps): JSX.Element;
|
|
34
|
+
export default CountdownCTA;
|
|
35
|
+
//# sourceMappingURL=CountdownCTA.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CountdownCTA.d.ts","sourceRoot":"","sources":["../../../../src/spec/registry/composites/CountdownCTA.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,iBAAkB,SAAQ,sBAAsB;IAC/D,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,sDAAsD;IACtD,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IAEpB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,4CAA4C;IAC5C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,kBAAkB;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,mBAAmB;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA6CD,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,OAAO,EACP,MAAM,EACN,WAAW,EACX,OAAO,EACP,YAAwB,EACxB,iBAAyB,EAAE,WAAW;AACtC,KAAiB,EACjB,OAAiB,EACjB,OAAe,EACf,SAAc,GACf,EAAE,iBAAiB,GAAG,GAAG,CAAC,OAAO,CAiHjC;AAED,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CountdownCTA Component
|
|
3
|
+
*
|
|
4
|
+
* Urgency countdown with action button.
|
|
5
|
+
*/
|
|
6
|
+
import React, { useState, useEffect } from 'react';
|
|
7
|
+
const colorClasses = {
|
|
8
|
+
primary: 'bg-purple-500 hover:bg-purple-600',
|
|
9
|
+
secondary: 'bg-gray-500 hover:bg-gray-600',
|
|
10
|
+
success: 'bg-green-500 hover:bg-green-600',
|
|
11
|
+
warning: 'bg-yellow-500 hover:bg-yellow-600',
|
|
12
|
+
error: 'bg-red-500 hover:bg-red-600',
|
|
13
|
+
muted: 'bg-gray-400 hover:bg-gray-500',
|
|
14
|
+
white: 'bg-white hover:bg-gray-100 text-black',
|
|
15
|
+
black: 'bg-black hover:bg-gray-900',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Breaks down milliseconds into time units.
|
|
19
|
+
*/
|
|
20
|
+
function getTimeUnits(ms) {
|
|
21
|
+
if (ms <= 0)
|
|
22
|
+
return [];
|
|
23
|
+
const seconds = Math.floor(ms / 1000);
|
|
24
|
+
const minutes = Math.floor(seconds / 60);
|
|
25
|
+
const hours = Math.floor(minutes / 60);
|
|
26
|
+
const days = Math.floor(hours / 24);
|
|
27
|
+
const units = [];
|
|
28
|
+
if (days > 0) {
|
|
29
|
+
units.push({ value: days, label: 'd' });
|
|
30
|
+
}
|
|
31
|
+
if (hours % 24 > 0 || days > 0) {
|
|
32
|
+
units.push({ value: hours % 24, label: 'h' });
|
|
33
|
+
}
|
|
34
|
+
if (minutes % 60 > 0 || hours > 0) {
|
|
35
|
+
units.push({ value: minutes % 60, label: 'm' });
|
|
36
|
+
}
|
|
37
|
+
units.push({ value: seconds % 60, label: 's' });
|
|
38
|
+
return units.slice(0, 4); // Max 4 units
|
|
39
|
+
}
|
|
40
|
+
export function CountdownCTA({ headline, subtext, endsAt, buttonLabel, onPress, expiredLabel = 'Expired', urgentThresholdMs = 60000, // 1 minute
|
|
41
|
+
color = 'primary', variant = 'solid', compact = false, className = '', }) {
|
|
42
|
+
const [timeRemaining, setTimeRemaining] = useState(0);
|
|
43
|
+
// Countdown timer
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const endTime = typeof endsAt === 'string' ? new Date(endsAt).getTime() : endsAt;
|
|
46
|
+
const update = () => {
|
|
47
|
+
const remaining = endTime - Date.now();
|
|
48
|
+
setTimeRemaining(remaining > 0 ? remaining : 0);
|
|
49
|
+
};
|
|
50
|
+
update();
|
|
51
|
+
const interval = setInterval(update, 1000);
|
|
52
|
+
return () => clearInterval(interval);
|
|
53
|
+
}, [endsAt]);
|
|
54
|
+
const isExpired = timeRemaining <= 0;
|
|
55
|
+
const isUrgent = timeRemaining > 0 && timeRemaining <= urgentThresholdMs;
|
|
56
|
+
const timeUnits = getTimeUnits(timeRemaining);
|
|
57
|
+
if (compact) {
|
|
58
|
+
return (React.createElement("div", { className: `
|
|
59
|
+
countdown-cta-compact flex items-center gap-3 p-3
|
|
60
|
+
bg-gray-800/90 backdrop-blur rounded-lg
|
|
61
|
+
${isUrgent ? 'animate-pulse' : ''}
|
|
62
|
+
${className}
|
|
63
|
+
` },
|
|
64
|
+
React.createElement("div", { className: `text-lg font-mono font-bold ${isUrgent ? 'text-red-400' : 'text-white'}` }, isExpired ? '--:--' : timeUnits.map((u) => String(u.value).padStart(2, '0')).join(':')),
|
|
65
|
+
React.createElement("button", { onClick: onPress, disabled: isExpired, className: `
|
|
66
|
+
px-4 py-2 rounded-lg font-semibold text-white
|
|
67
|
+
transition-all duration-200
|
|
68
|
+
${isExpired ? 'bg-gray-600 cursor-not-allowed' : colorClasses[color]}
|
|
69
|
+
` }, isExpired ? expiredLabel : buttonLabel)));
|
|
70
|
+
}
|
|
71
|
+
return (React.createElement("div", { className: `
|
|
72
|
+
countdown-cta bg-gray-800/90 backdrop-blur rounded-xl p-5
|
|
73
|
+
${isUrgent ? 'ring-2 ring-red-500/50' : ''}
|
|
74
|
+
${className}
|
|
75
|
+
` },
|
|
76
|
+
headline && (React.createElement("h3", { className: "text-white font-bold text-lg mb-1" }, headline)),
|
|
77
|
+
subtext && (React.createElement("p", { className: "text-gray-400 text-sm mb-4" }, subtext)),
|
|
78
|
+
React.createElement("div", { className: "flex justify-center gap-2 mb-4" }, isExpired ? (React.createElement("div", { className: "text-gray-500 text-2xl font-mono" }, "--:--:--")) : (timeUnits.map((unit, index) => (React.createElement("div", { key: unit.label, className: "text-center" },
|
|
79
|
+
React.createElement("div", { className: `
|
|
80
|
+
text-2xl font-mono font-bold
|
|
81
|
+
${isUrgent ? 'text-red-400' : 'text-white'}
|
|
82
|
+
` }, String(unit.value).padStart(2, '0')),
|
|
83
|
+
React.createElement("div", { className: "text-gray-500 text-xs uppercase" }, unit.label)))))),
|
|
84
|
+
isUrgent && !isExpired && (React.createElement("div", { className: "text-center text-red-400 text-sm mb-3 animate-pulse" }, "Hurry! Time is running out")),
|
|
85
|
+
React.createElement("button", { onClick: onPress, disabled: isExpired, className: `
|
|
86
|
+
w-full py-3 rounded-lg font-semibold text-white
|
|
87
|
+
transition-all duration-200
|
|
88
|
+
${isExpired ? 'bg-gray-600 cursor-not-allowed' : colorClasses[color]}
|
|
89
|
+
` }, isExpired ? expiredLabel : buttonLabel)));
|
|
90
|
+
}
|
|
91
|
+
export default CountdownCTA;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll Component
|
|
3
|
+
*
|
|
4
|
+
* Multi-choice voting with animated live results.
|
|
5
|
+
*/
|
|
6
|
+
import type { RegistryComponentProps } from '../ComponentRegistry';
|
|
7
|
+
import type { Color } from '../../types';
|
|
8
|
+
export interface PollOption {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
votes?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PollProps extends RegistryComponentProps {
|
|
14
|
+
/** Poll question */
|
|
15
|
+
question: string;
|
|
16
|
+
/** Poll options */
|
|
17
|
+
options: PollOption[] | string[];
|
|
18
|
+
/** Current results (votes per option) */
|
|
19
|
+
results?: number[];
|
|
20
|
+
/** Total votes */
|
|
21
|
+
totalVotes?: number;
|
|
22
|
+
/** Show results before voting */
|
|
23
|
+
showResultsBeforeVote?: boolean;
|
|
24
|
+
/** Whether the user has already voted */
|
|
25
|
+
hasVoted?: boolean;
|
|
26
|
+
/** The option ID the user voted for */
|
|
27
|
+
votedOptionId?: string;
|
|
28
|
+
/** Callback when user votes */
|
|
29
|
+
onVote?: (optionId: string) => void;
|
|
30
|
+
/** Accent color */
|
|
31
|
+
color?: Color;
|
|
32
|
+
/** Disabled state */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
/** Additional CSS classes */
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function Poll({ question, options: rawOptions, results, totalVotes: providedTotal, showResultsBeforeVote, hasVoted, votedOptionId, onVote, color, disabled, className, }: PollProps): JSX.Element;
|
|
38
|
+
export default Poll;
|
|
39
|
+
//# sourceMappingURL=Poll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Poll.d.ts","sourceRoot":"","sources":["../../../../src/spec/registry/composites/Poll.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAU,KAAK,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAU,SAAQ,sBAAsB;IACvD,oBAAoB;IACpB,QAAQ,EAAE,MAAM,CAAC;IAEjB,mBAAmB;IACnB,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,CAAC;IAEjC,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,iCAAiC;IACjC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,+BAA+B;IAC/B,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpC,mBAAmB;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAyBD,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,OAAO,EAAE,UAAU,EACnB,OAAY,EACZ,UAAU,EAAE,aAAa,EACzB,qBAA6B,EAC7B,QAAgB,EAChB,aAAa,EACb,MAAM,EACN,KAAiB,EACjB,QAAgB,EAChB,SAAc,GACf,EAAE,SAAS,GAAG,GAAG,CAAC,OAAO,CAgGzB;AAED,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll Component
|
|
3
|
+
*
|
|
4
|
+
* Multi-choice voting with animated live results.
|
|
5
|
+
*/
|
|
6
|
+
import React, { useState, useCallback } from 'react';
|
|
7
|
+
const colorClasses = {
|
|
8
|
+
primary: 'bg-purple-500',
|
|
9
|
+
secondary: 'bg-gray-500',
|
|
10
|
+
success: 'bg-green-500',
|
|
11
|
+
warning: 'bg-yellow-500',
|
|
12
|
+
error: 'bg-red-500',
|
|
13
|
+
muted: 'bg-gray-400',
|
|
14
|
+
white: 'bg-white',
|
|
15
|
+
black: 'bg-black',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes options to a consistent format.
|
|
19
|
+
*/
|
|
20
|
+
function normalizeOptions(options) {
|
|
21
|
+
return options.map((opt, index) => {
|
|
22
|
+
if (typeof opt === 'string') {
|
|
23
|
+
return { id: String(index), label: opt };
|
|
24
|
+
}
|
|
25
|
+
return opt;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function Poll({ question, options: rawOptions, results = [], totalVotes: providedTotal, showResultsBeforeVote = false, hasVoted = false, votedOptionId, onVote, color = 'primary', disabled = false, className = '', }) {
|
|
29
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
30
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
31
|
+
const options = normalizeOptions(rawOptions);
|
|
32
|
+
// Calculate total votes
|
|
33
|
+
const totalVotes = providedTotal ?? results.reduce((sum, v) => sum + v, 0);
|
|
34
|
+
// Should we show results?
|
|
35
|
+
const showResults = hasVoted || showResultsBeforeVote;
|
|
36
|
+
// Handle option click
|
|
37
|
+
const handleOptionClick = useCallback((optionId) => {
|
|
38
|
+
if (disabled || hasVoted || isSubmitting)
|
|
39
|
+
return;
|
|
40
|
+
if (onVote) {
|
|
41
|
+
setIsSubmitting(true);
|
|
42
|
+
setSelectedId(optionId);
|
|
43
|
+
onVote(optionId);
|
|
44
|
+
}
|
|
45
|
+
}, [disabled, hasVoted, isSubmitting, onVote]);
|
|
46
|
+
return (React.createElement("div", { className: `poll-component bg-gray-800/90 backdrop-blur rounded-xl p-4 ${className}` },
|
|
47
|
+
React.createElement("h3", { className: "text-white font-semibold text-lg mb-4" }, question),
|
|
48
|
+
React.createElement("div", { className: "space-y-2" }, options.map((option, index) => {
|
|
49
|
+
const votes = results[index] ?? 0;
|
|
50
|
+
const percentage = totalVotes > 0 ? (votes / totalVotes) * 100 : 0;
|
|
51
|
+
const isSelected = option.id === selectedId;
|
|
52
|
+
const isVotedFor = option.id === votedOptionId;
|
|
53
|
+
const canClick = !disabled && !hasVoted && !isSubmitting;
|
|
54
|
+
return (React.createElement("button", { key: option.id, onClick: () => handleOptionClick(option.id), disabled: !canClick, className: `
|
|
55
|
+
w-full relative overflow-hidden rounded-lg p-3 text-left
|
|
56
|
+
transition-all duration-200
|
|
57
|
+
${canClick ? 'hover:bg-gray-700/50 cursor-pointer' : 'cursor-default'}
|
|
58
|
+
${isSelected ? 'ring-2 ring-purple-500' : ''}
|
|
59
|
+
${isVotedFor ? 'ring-2 ring-green-500' : ''}
|
|
60
|
+
bg-gray-700/30
|
|
61
|
+
` },
|
|
62
|
+
showResults && (React.createElement("div", { className: `absolute inset-y-0 left-0 ${colorClasses[color]} opacity-30 transition-all duration-500`, style: { width: `${percentage}%` } })),
|
|
63
|
+
React.createElement("div", { className: "relative flex items-center justify-between" },
|
|
64
|
+
React.createElement("span", { className: "text-white font-medium" }, option.label),
|
|
65
|
+
showResults && (React.createElement("span", { className: "text-white/70 text-sm" },
|
|
66
|
+
Math.round(percentage),
|
|
67
|
+
"%")),
|
|
68
|
+
isVotedFor && (React.createElement("span", { className: "ml-2 text-green-400" }, "\u2713")))));
|
|
69
|
+
})),
|
|
70
|
+
showResults && totalVotes > 0 && (React.createElement("div", { className: "mt-3 text-center text-gray-400 text-sm" },
|
|
71
|
+
totalVotes.toLocaleString(),
|
|
72
|
+
" ",
|
|
73
|
+
totalVotes === 1 ? 'vote' : 'votes')),
|
|
74
|
+
isSubmitting && !hasVoted && (React.createElement("div", { className: "mt-2 text-center text-gray-400 text-sm" }, "Submitting..."))));
|
|
75
|
+
}
|
|
76
|
+
export default Poll;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prediction Component
|
|
3
|
+
*
|
|
4
|
+
* Binary choice prediction with countdown and outcome reveal.
|
|
5
|
+
*/
|
|
6
|
+
import type { RegistryComponentProps } from '../ComponentRegistry';
|
|
7
|
+
import type { Color } from '../../types';
|
|
8
|
+
export interface PredictionOption {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
odds?: number;
|
|
12
|
+
percentage?: number;
|
|
13
|
+
}
|
|
14
|
+
export type PredictionStatus = 'open' | 'locked' | 'resolved';
|
|
15
|
+
export interface PredictionProps extends RegistryComponentProps {
|
|
16
|
+
/** Prediction question */
|
|
17
|
+
question: string;
|
|
18
|
+
/** Two options */
|
|
19
|
+
options: [PredictionOption, PredictionOption] | [string, string];
|
|
20
|
+
/** Current status */
|
|
21
|
+
status?: PredictionStatus;
|
|
22
|
+
/** Winning option ID (when resolved) */
|
|
23
|
+
winnerId?: string;
|
|
24
|
+
/** User's prediction option ID */
|
|
25
|
+
userPredictionId?: string;
|
|
26
|
+
/** Countdown end time (ISO string or ms timestamp) */
|
|
27
|
+
locksAt?: string | number;
|
|
28
|
+
/** Callback when user predicts */
|
|
29
|
+
onPredict?: (optionId: string) => void;
|
|
30
|
+
/** Color scheme */
|
|
31
|
+
colors?: [Color, Color];
|
|
32
|
+
/** Additional CSS classes */
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function Prediction({ question, options: rawOptions, status, winnerId, userPredictionId, locksAt, onPredict, colors, className, }: PredictionProps): JSX.Element;
|
|
36
|
+
export default Prediction;
|
|
37
|
+
//# sourceMappingURL=Prediction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Prediction.d.ts","sourceRoot":"","sources":["../../../../src/spec/registry/composites/Prediction.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE9D,MAAM,WAAW,eAAgB,SAAQ,sBAAsB;IAC7D,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IAEjB,kBAAkB;IAClB,OAAO,EAAE,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjE,qBAAqB;IACrB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAE1B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1B,kCAAkC;IAClC,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAEvC,mBAAmB;IACnB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAExB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2CD,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,OAAO,EAAE,UAAU,EACnB,MAAe,EACf,QAAQ,EACR,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,MAA6B,EAC7B,SAAc,GACf,EAAE,eAAe,GAAG,GAAG,CAAC,OAAO,CAuI/B;AAED,eAAe,UAAU,CAAC"}
|