tyrell-react 1.0.0-TC7
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/LICENSE +21 -0
- package/README.md +386 -0
- package/dist/components/TyButton.d.ts +50 -0
- package/dist/components/TyButton.d.ts.map +1 -0
- package/dist/components/TyButton.js +63 -0
- package/dist/components/TyButton.js.map +1 -0
- package/dist/components/TyCalendar.d.ts +63 -0
- package/dist/components/TyCalendar.d.ts.map +1 -0
- package/dist/components/TyCalendar.js +122 -0
- package/dist/components/TyCalendar.js.map +1 -0
- package/dist/components/TyCalendarMonth.d.ts +32 -0
- package/dist/components/TyCalendarMonth.d.ts.map +1 -0
- package/dist/components/TyCalendarMonth.js +54 -0
- package/dist/components/TyCalendarMonth.js.map +1 -0
- package/dist/components/TyCalendarNavigation.d.ts +21 -0
- package/dist/components/TyCalendarNavigation.d.ts.map +1 -0
- package/dist/components/TyCalendarNavigation.js +50 -0
- package/dist/components/TyCalendarNavigation.js.map +1 -0
- package/dist/components/TyCheckbox.d.ts +39 -0
- package/dist/components/TyCheckbox.d.ts.map +1 -0
- package/dist/components/TyCheckbox.js +68 -0
- package/dist/components/TyCheckbox.js.map +1 -0
- package/dist/components/TyCopy.d.ts +21 -0
- package/dist/components/TyCopy.d.ts.map +1 -0
- package/dist/components/TyCopy.js +42 -0
- package/dist/components/TyCopy.js.map +1 -0
- package/dist/components/TyDatePicker.d.ts +45 -0
- package/dist/components/TyDatePicker.d.ts.map +1 -0
- package/dist/components/TyDatePicker.js +114 -0
- package/dist/components/TyDatePicker.js.map +1 -0
- package/dist/components/TyDropdown.d.ts +51 -0
- package/dist/components/TyDropdown.d.ts.map +1 -0
- package/dist/components/TyDropdown.js +97 -0
- package/dist/components/TyDropdown.js.map +1 -0
- package/dist/components/TyIcon.d.ts +17 -0
- package/dist/components/TyIcon.d.ts.map +1 -0
- package/dist/components/TyIcon.js +41 -0
- package/dist/components/TyIcon.js.map +1 -0
- package/dist/components/TyInput.d.ts +65 -0
- package/dist/components/TyInput.d.ts.map +1 -0
- package/dist/components/TyInput.js +92 -0
- package/dist/components/TyInput.js.map +1 -0
- package/dist/components/TyModal.d.ts +29 -0
- package/dist/components/TyModal.d.ts.map +1 -0
- package/dist/components/TyModal.js +74 -0
- package/dist/components/TyModal.js.map +1 -0
- package/dist/components/TyMultiselect.d.ts +51 -0
- package/dist/components/TyMultiselect.d.ts.map +1 -0
- package/dist/components/TyMultiselect.js +92 -0
- package/dist/components/TyMultiselect.js.map +1 -0
- package/dist/components/TyOption.d.ts +10 -0
- package/dist/components/TyOption.d.ts.map +1 -0
- package/dist/components/TyOption.js +25 -0
- package/dist/components/TyOption.js.map +1 -0
- package/dist/components/TyPopup.d.ts +24 -0
- package/dist/components/TyPopup.d.ts.map +1 -0
- package/dist/components/TyPopup.js +61 -0
- package/dist/components/TyPopup.js.map +1 -0
- package/dist/components/TyResizeObserver.d.ts +11 -0
- package/dist/components/TyResizeObserver.d.ts.map +1 -0
- package/dist/components/TyResizeObserver.js +28 -0
- package/dist/components/TyResizeObserver.js.map +1 -0
- package/dist/components/TyScrollContainer.d.ts +25 -0
- package/dist/components/TyScrollContainer.d.ts.map +1 -0
- package/dist/components/TyScrollContainer.js +43 -0
- package/dist/components/TyScrollContainer.js.map +1 -0
- package/dist/components/TyStep.d.ts +17 -0
- package/dist/components/TyStep.d.ts.map +1 -0
- package/dist/components/TyStep.js +35 -0
- package/dist/components/TyStep.js.map +1 -0
- package/dist/components/TyTab.d.ts +13 -0
- package/dist/components/TyTab.d.ts.map +1 -0
- package/dist/components/TyTab.js +32 -0
- package/dist/components/TyTab.js.map +1 -0
- package/dist/components/TyTabs.d.ts +23 -0
- package/dist/components/TyTabs.d.ts.map +1 -0
- package/dist/components/TyTabs.js +48 -0
- package/dist/components/TyTabs.js.map +1 -0
- package/dist/components/TyTag.d.ts +22 -0
- package/dist/components/TyTag.d.ts.map +1 -0
- package/dist/components/TyTag.js +45 -0
- package/dist/components/TyTag.js.map +1 -0
- package/dist/components/TyTextarea.d.ts +37 -0
- package/dist/components/TyTextarea.d.ts.map +1 -0
- package/dist/components/TyTextarea.js +83 -0
- package/dist/components/TyTextarea.js.map +1 -0
- package/dist/components/TyTooltip.d.ts +17 -0
- package/dist/components/TyTooltip.d.ts.map +1 -0
- package/dist/components/TyTooltip.js +40 -0
- package/dist/components/TyTooltip.js.map +1 -0
- package/dist/components/TyWizard.d.ts +26 -0
- package/dist/components/TyWizard.d.ts.map +1 -0
- package/dist/components/TyWizard.js +50 -0
- package/dist/components/TyWizard.js.map +1 -0
- package/dist/components/index.d.ts +93 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +106 -0
- package/dist/components/index.js.map +1 -0
- package/package.json +46 -0
- package/src/components/EventConventionTest.tsx +155 -0
- package/src/components/TyButton.tsx +140 -0
- package/src/components/TyCalendar.tsx +244 -0
- package/src/components/TyCalendarMonth.tsx +108 -0
- package/src/components/TyCalendarNavigation.tsx +91 -0
- package/src/components/TyCheckbox.tsx +138 -0
- package/src/components/TyCopy.tsx +78 -0
- package/src/components/TyDatePicker.tsx +216 -0
- package/src/components/TyDropdown.tsx +205 -0
- package/src/components/TyIcon.tsx +72 -0
- package/src/components/TyInput.tsx +194 -0
- package/src/components/TyModal.tsx +142 -0
- package/src/components/TyMultiselect.tsx +189 -0
- package/src/components/TyOption.tsx +42 -0
- package/src/components/TyPopup.tsx +111 -0
- package/src/components/TyResizeObserver.tsx +54 -0
- package/src/components/TyScrollContainer.tsx +87 -0
- package/src/components/TyStep.tsx +71 -0
- package/src/components/TyTab.tsx +63 -0
- package/src/components/TyTabs.tsx +93 -0
- package/src/components/TyTag.tsx +79 -0
- package/src/components/TyTextarea.tsx +144 -0
- package/src/components/TyTooltip.tsx +83 -0
- package/src/components/TyWizard.tsx +99 -0
- package/src/components/index.ts +230 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
// Option data structure for data-driven approach
|
|
4
|
+
export interface OptionData {
|
|
5
|
+
value: string;
|
|
6
|
+
text: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Event detail structure for dropdown events
|
|
11
|
+
export interface TyDropdownEventDetail {
|
|
12
|
+
option: HTMLElement;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Type definitions for Ty Dropdown component
|
|
16
|
+
export interface TyDropdownProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'style'> {
|
|
17
|
+
style?: import('./TyInput').TyInputCSSProperties;
|
|
18
|
+
/** Semantic styling variant */
|
|
19
|
+
flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
|
|
20
|
+
|
|
21
|
+
/** Dropdown size */
|
|
22
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
23
|
+
|
|
24
|
+
/** Selected value */
|
|
25
|
+
value?: string;
|
|
26
|
+
|
|
27
|
+
/** Placeholder text */
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
|
|
30
|
+
/** Dropdown label */
|
|
31
|
+
label?: string;
|
|
32
|
+
|
|
33
|
+
/** Disable the dropdown */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
|
|
36
|
+
/** Make dropdown readonly */
|
|
37
|
+
readonly?: boolean;
|
|
38
|
+
|
|
39
|
+
/** Required field */
|
|
40
|
+
required?: boolean;
|
|
41
|
+
|
|
42
|
+
/** Enable search functionality */
|
|
43
|
+
searchable?: boolean;
|
|
44
|
+
|
|
45
|
+
/** Show clear button */
|
|
46
|
+
clearable?: boolean;
|
|
47
|
+
|
|
48
|
+
/** Disable clear button (alias for clearable={false}) */
|
|
49
|
+
notClearable?: boolean;
|
|
50
|
+
|
|
51
|
+
/** Debounce in milliseconds (0-5000) */
|
|
52
|
+
debounce?: number;
|
|
53
|
+
|
|
54
|
+
/** Disable search functionality (ClojureScript: not-searchable) */
|
|
55
|
+
notSearchable?: boolean;
|
|
56
|
+
|
|
57
|
+
/** @deprecated Use notSearchable instead. External search handling */
|
|
58
|
+
externalSearch?: boolean;
|
|
59
|
+
|
|
60
|
+
/** Form field name for form submission */
|
|
61
|
+
name?: string;
|
|
62
|
+
|
|
63
|
+
// Data-driven approach
|
|
64
|
+
options?: OptionData[];
|
|
65
|
+
|
|
66
|
+
// React event handlers
|
|
67
|
+
onChange?: (event: CustomEvent<TyDropdownEventDetail>) => void;
|
|
68
|
+
onSearch?: (event: CustomEvent<{ query: string; element: HTMLElement }>) => void;
|
|
69
|
+
|
|
70
|
+
// Children for slotted approach
|
|
71
|
+
children?: React.ReactNode;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// React wrapper for ty-dropdown web component
|
|
75
|
+
export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
|
|
76
|
+
({
|
|
77
|
+
options,
|
|
78
|
+
children,
|
|
79
|
+
onChange,
|
|
80
|
+
onSearch,
|
|
81
|
+
disabled,
|
|
82
|
+
notSearchable,
|
|
83
|
+
externalSearch,
|
|
84
|
+
clearable,
|
|
85
|
+
notClearable,
|
|
86
|
+
debounce,
|
|
87
|
+
name,
|
|
88
|
+
...props
|
|
89
|
+
}, ref) => {
|
|
90
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
91
|
+
|
|
92
|
+
const handleChange = useCallback((event: CustomEvent<TyDropdownEventDetail>) => {
|
|
93
|
+
if (onChange) {
|
|
94
|
+
onChange(event);
|
|
95
|
+
}
|
|
96
|
+
}, [onChange]);
|
|
97
|
+
|
|
98
|
+
const handleSearch = useCallback((event: CustomEvent<{ query: string; element: HTMLElement }>) => {
|
|
99
|
+
if (onSearch) {
|
|
100
|
+
onSearch(event);
|
|
101
|
+
}
|
|
102
|
+
}, [onSearch]);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const element = elementRef.current;
|
|
106
|
+
if (!element) return;
|
|
107
|
+
|
|
108
|
+
// Listen for custom events from ty-dropdown
|
|
109
|
+
if (onChange) {
|
|
110
|
+
element.addEventListener('change', handleChange as EventListener);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (onSearch) {
|
|
114
|
+
element.addEventListener('search', handleSearch as EventListener);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return () => {
|
|
118
|
+
if (onChange) {
|
|
119
|
+
element.removeEventListener('change', handleChange as EventListener);
|
|
120
|
+
}
|
|
121
|
+
if (onSearch) {
|
|
122
|
+
element.removeEventListener('search', handleSearch as EventListener);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}, [handleChange, handleSearch, onChange, onSearch]);
|
|
126
|
+
|
|
127
|
+
// Handle ref forwarding
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (ref && elementRef.current) {
|
|
130
|
+
if (typeof ref === 'function') {
|
|
131
|
+
ref(elementRef.current);
|
|
132
|
+
} else {
|
|
133
|
+
ref.current = elementRef.current;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}, [ref]);
|
|
137
|
+
|
|
138
|
+
// Render options from data if provided, otherwise use children
|
|
139
|
+
const renderContent = () => {
|
|
140
|
+
if (options && options.length > 0) {
|
|
141
|
+
// Data-driven approach - create ty-option elements
|
|
142
|
+
return options.map((option, index) =>
|
|
143
|
+
React.createElement(
|
|
144
|
+
'ty-option',
|
|
145
|
+
{
|
|
146
|
+
key: option.value || index,
|
|
147
|
+
value: option.value,
|
|
148
|
+
...(option.disabled && { disabled: "" }),
|
|
149
|
+
},
|
|
150
|
+
option.text
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Slotted approach - use provided children
|
|
156
|
+
return children;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Convert React props to web component attributes
|
|
160
|
+
const webComponentProps: Record<string, any> = {
|
|
161
|
+
...props,
|
|
162
|
+
ref: elementRef,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Add conditional attributes
|
|
166
|
+
if (disabled) webComponentProps.disabled = '';
|
|
167
|
+
|
|
168
|
+
// Handle search functionality (prefer not-searchable over deprecated externalSearch)
|
|
169
|
+
if (notSearchable) {
|
|
170
|
+
webComponentProps['not-searchable'] = '';
|
|
171
|
+
} else if (externalSearch) {
|
|
172
|
+
// Support deprecated externalSearch for backward compatibility
|
|
173
|
+
webComponentProps['not-searchable'] = '';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle clearable functionality
|
|
177
|
+
if (clearable !== undefined) {
|
|
178
|
+
if (clearable) {
|
|
179
|
+
webComponentProps.clearable = '';
|
|
180
|
+
} else {
|
|
181
|
+
webComponentProps['not-clearable'] = '';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (notClearable) {
|
|
186
|
+
webComponentProps['not-clearable'] = '';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Add debounce attribute
|
|
190
|
+
if (debounce !== undefined) {
|
|
191
|
+
webComponentProps.debounce = debounce;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Add string attributes
|
|
195
|
+
if (name) webComponentProps.name = name;
|
|
196
|
+
|
|
197
|
+
return React.createElement(
|
|
198
|
+
'ty-dropdown',
|
|
199
|
+
webComponentProps,
|
|
200
|
+
renderContent()
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
TyDropdown.displayName = 'TyDropdown';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
// Type definitions for Ty Icon component
|
|
4
|
+
export interface TyIconProps extends React.HTMLAttributes<HTMLElement> {
|
|
5
|
+
/** Icon name from the icon registry (e.g., 'home', 'star', 'settings') */
|
|
6
|
+
name: string;
|
|
7
|
+
|
|
8
|
+
/** Icon size - relative (em-based) or absolute (pixel-based) */
|
|
9
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '12' | '14' | '16' | '18' | '20' | '24' | '32' | '48';
|
|
10
|
+
|
|
11
|
+
/** Enable spinning animation */
|
|
12
|
+
spin?: boolean;
|
|
13
|
+
|
|
14
|
+
/** Enable pulse animation */
|
|
15
|
+
pulse?: boolean;
|
|
16
|
+
|
|
17
|
+
/** Animation tempo/speed */
|
|
18
|
+
tempo?: 'slow' | 'fast';
|
|
19
|
+
|
|
20
|
+
/** Additional CSS classes */
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// React wrapper for ty-icon web component
|
|
25
|
+
export const TyIcon = React.forwardRef<HTMLElement, TyIconProps>(
|
|
26
|
+
({ name, size, spin, pulse, tempo, className, ...props }, ref) => {
|
|
27
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
28
|
+
|
|
29
|
+
// Handle ref forwarding
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (ref && elementRef.current) {
|
|
32
|
+
if (typeof ref === 'function') {
|
|
33
|
+
ref(elementRef.current);
|
|
34
|
+
} else {
|
|
35
|
+
ref.current = elementRef.current;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, [ref]);
|
|
39
|
+
|
|
40
|
+
// Convert React props to web component attributes
|
|
41
|
+
const webComponentProps: Record<string, any> = {
|
|
42
|
+
...props,
|
|
43
|
+
name,
|
|
44
|
+
ref: elementRef,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Add optional attributes only if they have values
|
|
48
|
+
if (size) {
|
|
49
|
+
webComponentProps.size = size;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (spin) {
|
|
53
|
+
webComponentProps.spin = ''; // Boolean attributes as empty string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (pulse) {
|
|
57
|
+
webComponentProps.pulse = ''; // Boolean attributes as empty string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (tempo) {
|
|
61
|
+
webComponentProps.tempo = tempo;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (className) {
|
|
65
|
+
webComponentProps.class = className; // HTML attribute is 'class', not 'className'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return React.createElement('ty-icon', webComponentProps);
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
TyIcon.displayName = 'TyIcon';
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
// Event detail structure for ty-input events
|
|
4
|
+
export interface TyInputEventDetail {
|
|
5
|
+
value: any; // shadow value (processed/parsed)
|
|
6
|
+
formattedValue: string; // user-visible formatted value
|
|
7
|
+
rawValue: string; // raw input value
|
|
8
|
+
originalEvent: Event; // original DOM event
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TyInputCSSProperties extends React.CSSProperties {
|
|
12
|
+
'--input-bg'?: string;
|
|
13
|
+
'--input-color'?: string;
|
|
14
|
+
'--input-border'?: string;
|
|
15
|
+
'--input-border-hover'?: string;
|
|
16
|
+
'--input-border-focus'?: string;
|
|
17
|
+
'--input-shadow-focus'?: string;
|
|
18
|
+
'--input-placeholder'?: string;
|
|
19
|
+
'--input-disabled-bg'?: string;
|
|
20
|
+
'--input-disabled-border'?: string;
|
|
21
|
+
'--input-disabled-color'?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Type definitions for Ty Input component
|
|
25
|
+
export interface TyInputProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'onFocus' | 'onBlur' | 'style'> {
|
|
26
|
+
style?: TyInputCSSProperties;
|
|
27
|
+
/** Input type */
|
|
28
|
+
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search'
|
|
29
|
+
| 'currency' | 'percent' | 'compact';
|
|
30
|
+
|
|
31
|
+
/** Semantic styling variant */
|
|
32
|
+
flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
|
|
33
|
+
|
|
34
|
+
/** Input size */
|
|
35
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
36
|
+
|
|
37
|
+
/** Input value */
|
|
38
|
+
value?: string;
|
|
39
|
+
|
|
40
|
+
/** Placeholder text */
|
|
41
|
+
placeholder?: string;
|
|
42
|
+
|
|
43
|
+
/** Input label */
|
|
44
|
+
label?: string;
|
|
45
|
+
|
|
46
|
+
/** Error message */
|
|
47
|
+
error?: string;
|
|
48
|
+
|
|
49
|
+
/** Disable the input */
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
|
|
52
|
+
/** Required field */
|
|
53
|
+
required?: boolean;
|
|
54
|
+
|
|
55
|
+
/** Form field name for form submission */
|
|
56
|
+
name?: string;
|
|
57
|
+
|
|
58
|
+
/** Checked state for checkbox inputs */
|
|
59
|
+
checked?: boolean;
|
|
60
|
+
|
|
61
|
+
// Numeric formatting props
|
|
62
|
+
currency?: string;
|
|
63
|
+
locale?: string;
|
|
64
|
+
precision?: string | number;
|
|
65
|
+
|
|
66
|
+
/** Debounce in milliseconds (0-5000) */
|
|
67
|
+
debounce?: number;
|
|
68
|
+
|
|
69
|
+
// React event handlers - override with our custom types
|
|
70
|
+
/**
|
|
71
|
+
* Fires on every keystroke (React convention)
|
|
72
|
+
* Maps to native 'input' event from ty-input
|
|
73
|
+
*/
|
|
74
|
+
onChange?: (event: CustomEvent<TyInputEventDetail>) => void;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Fires on blur if value changed (native DOM behavior)
|
|
78
|
+
* Maps to native 'change' event from ty-input
|
|
79
|
+
*/
|
|
80
|
+
onChangeCommit?: (event: CustomEvent<TyInputEventDetail>) => void;
|
|
81
|
+
|
|
82
|
+
/** Standard focus event */
|
|
83
|
+
onFocus?: (event: FocusEvent) => void;
|
|
84
|
+
|
|
85
|
+
/** Standard blur event */
|
|
86
|
+
onBlur?: (event: FocusEvent) => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// React wrapper for ty-input web component
|
|
90
|
+
export const TyInput = React.forwardRef<HTMLElement, TyInputProps>(
|
|
91
|
+
({ onChange, onChangeCommit, onFocus, onBlur, disabled, name, checked, debounce, ...props }, ref) => {
|
|
92
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
93
|
+
|
|
94
|
+
// Map onChange to input event (React convention)
|
|
95
|
+
const handleInput = useCallback((event: CustomEvent<TyInputEventDetail>) => {
|
|
96
|
+
if (onChange) {
|
|
97
|
+
onChange(event);
|
|
98
|
+
}
|
|
99
|
+
}, [onChange]);
|
|
100
|
+
|
|
101
|
+
// Map onChangeCommit to change event (blur behavior)
|
|
102
|
+
const handleChangeCommit = useCallback((event: CustomEvent<TyInputEventDetail>) => {
|
|
103
|
+
if (onChangeCommit) {
|
|
104
|
+
onChangeCommit(event);
|
|
105
|
+
}
|
|
106
|
+
}, [onChangeCommit]);
|
|
107
|
+
|
|
108
|
+
const handleFocus = useCallback((event: FocusEvent) => {
|
|
109
|
+
if (onFocus) {
|
|
110
|
+
onFocus(event);
|
|
111
|
+
}
|
|
112
|
+
}, [onFocus]);
|
|
113
|
+
|
|
114
|
+
const handleBlur = useCallback((event: FocusEvent) => {
|
|
115
|
+
if (onBlur) {
|
|
116
|
+
onBlur(event);
|
|
117
|
+
}
|
|
118
|
+
}, [onBlur]);
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
const element = elementRef.current;
|
|
122
|
+
if (!element) return;
|
|
123
|
+
|
|
124
|
+
// Listen for custom input/change events from ty-input
|
|
125
|
+
// Map onChange → input event (React convention)
|
|
126
|
+
if (onChange) {
|
|
127
|
+
element.addEventListener('input', handleInput as EventListener);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Map onChangeCommit → change event (blur behavior)
|
|
131
|
+
if (onChangeCommit) {
|
|
132
|
+
element.addEventListener('change', handleChangeCommit as EventListener);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Listen for standard focus/blur events
|
|
136
|
+
if (onFocus) {
|
|
137
|
+
element.addEventListener('focus', handleFocus as EventListener);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (onBlur) {
|
|
141
|
+
element.addEventListener('blur', handleBlur as EventListener);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return () => {
|
|
145
|
+
if (onChange) {
|
|
146
|
+
element.removeEventListener('input', handleInput as EventListener);
|
|
147
|
+
}
|
|
148
|
+
if (onChangeCommit) {
|
|
149
|
+
element.removeEventListener('change', handleChangeCommit as EventListener);
|
|
150
|
+
}
|
|
151
|
+
if (onFocus) {
|
|
152
|
+
element.removeEventListener('focus', handleFocus as EventListener);
|
|
153
|
+
}
|
|
154
|
+
if (onBlur) {
|
|
155
|
+
element.removeEventListener('blur', handleBlur as EventListener);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}, [handleInput, handleChangeCommit, handleFocus, handleBlur, onChange, onChangeCommit, onFocus, onBlur]);
|
|
159
|
+
|
|
160
|
+
// Handle ref forwarding
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (ref && elementRef.current) {
|
|
163
|
+
if (typeof ref === 'function') {
|
|
164
|
+
ref(elementRef.current);
|
|
165
|
+
} else {
|
|
166
|
+
ref.current = elementRef.current;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}, [ref]);
|
|
170
|
+
|
|
171
|
+
// Convert React props to web component attributes
|
|
172
|
+
const webComponentProps: Record<string, any> = {
|
|
173
|
+
...props,
|
|
174
|
+
ref: elementRef,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Add conditional attributes
|
|
178
|
+
if (disabled) webComponentProps.disabled = '';
|
|
179
|
+
if (checked) webComponentProps.checked = '';
|
|
180
|
+
|
|
181
|
+
// Add string attributes
|
|
182
|
+
if (name) webComponentProps.name = name;
|
|
183
|
+
|
|
184
|
+
// Add debounce attribute
|
|
185
|
+
if (debounce !== undefined) webComponentProps.debounce = debounce;
|
|
186
|
+
|
|
187
|
+
return React.createElement(
|
|
188
|
+
'ty-input',
|
|
189
|
+
webComponentProps
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
TyInput.displayName = 'TyInput';
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useImperativeHandle } from 'react';
|
|
2
|
+
|
|
3
|
+
// Event detail structure for modal events
|
|
4
|
+
export interface TyModalEventDetail {
|
|
5
|
+
reason?: 'programmatic' | 'native' | 'backdrop' | 'escape' | 'close-button';
|
|
6
|
+
returnValue?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Type definitions for Ty Modal component
|
|
10
|
+
export interface TyModalProps extends React.HTMLAttributes<HTMLElement> {
|
|
11
|
+
/** Controls modal visibility */
|
|
12
|
+
open?: boolean;
|
|
13
|
+
|
|
14
|
+
/** Show backdrop behind modal (default: true) */
|
|
15
|
+
backdrop?: boolean;
|
|
16
|
+
|
|
17
|
+
/** Allow closing modal by clicking backdrop (default: true) */
|
|
18
|
+
closeOnOutsideClick?: boolean;
|
|
19
|
+
|
|
20
|
+
/** Allow closing modal with Escape key (default: true) */
|
|
21
|
+
closeOnEscape?: boolean;
|
|
22
|
+
|
|
23
|
+
/** Require confirmation before closing when there are unsaved changes */
|
|
24
|
+
protected?: boolean;
|
|
25
|
+
|
|
26
|
+
/** React event handlers */
|
|
27
|
+
onOpen?: (event: CustomEvent<TyModalEventDetail>) => void;
|
|
28
|
+
onClose?: (event: CustomEvent<TyModalEventDetail>) => void;
|
|
29
|
+
|
|
30
|
+
/** Modal content */
|
|
31
|
+
children?: React.ReactNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Ref interface for imperative methods
|
|
35
|
+
export interface TyModalRef {
|
|
36
|
+
show: () => void;
|
|
37
|
+
hide: () => void;
|
|
38
|
+
element: HTMLElement | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// React wrapper for ty-modal web component
|
|
42
|
+
export const TyModal = React.forwardRef<TyModalRef, TyModalProps>(
|
|
43
|
+
({
|
|
44
|
+
open,
|
|
45
|
+
backdrop,
|
|
46
|
+
closeOnOutsideClick,
|
|
47
|
+
closeOnEscape,
|
|
48
|
+
protected: isProtected,
|
|
49
|
+
onOpen,
|
|
50
|
+
onClose,
|
|
51
|
+
children,
|
|
52
|
+
...props
|
|
53
|
+
}, ref) => {
|
|
54
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
55
|
+
|
|
56
|
+
// Expose imperative methods through ref
|
|
57
|
+
useImperativeHandle(ref, () => ({
|
|
58
|
+
show: () => {
|
|
59
|
+
if (elementRef.current && typeof (elementRef.current as any).show === 'function') {
|
|
60
|
+
(elementRef.current as any).show();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
hide: () => {
|
|
64
|
+
if (elementRef.current && typeof (elementRef.current as any).hide === 'function') {
|
|
65
|
+
(elementRef.current as any).hide();
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
element: elementRef.current,
|
|
69
|
+
}), []);
|
|
70
|
+
|
|
71
|
+
// Handle modal events
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const element = elementRef.current;
|
|
74
|
+
if (!element) return;
|
|
75
|
+
|
|
76
|
+
const handleOpen = (event: CustomEvent<TyModalEventDetail>) => {
|
|
77
|
+
if (onOpen) {
|
|
78
|
+
onOpen(event);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleClose = (event: CustomEvent<TyModalEventDetail>) => {
|
|
83
|
+
if (onClose) {
|
|
84
|
+
onClose(event);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Listen for custom modal events
|
|
89
|
+
if (onOpen) {
|
|
90
|
+
element.addEventListener('open', handleOpen as EventListener);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (onClose) {
|
|
94
|
+
element.addEventListener('close', handleClose as EventListener);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return () => {
|
|
98
|
+
if (onOpen) {
|
|
99
|
+
element.removeEventListener('open', handleOpen as EventListener);
|
|
100
|
+
}
|
|
101
|
+
if (onClose) {
|
|
102
|
+
element.removeEventListener('close', handleClose as EventListener);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}, [onOpen, onClose]);
|
|
106
|
+
|
|
107
|
+
// Convert React props to web component attributes
|
|
108
|
+
const webComponentProps: Record<string, any> = {
|
|
109
|
+
...props,
|
|
110
|
+
ref: elementRef,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Add boolean attributes using correct HTML attribute names
|
|
114
|
+
if (open) {
|
|
115
|
+
webComponentProps.open = ''; // Boolean attributes as empty string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (backdrop === false) { // Only set if explicitly false (default is true)
|
|
119
|
+
webComponentProps.backdrop = 'false';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (closeOnOutsideClick === false) { // Only set if explicitly false (default is true)
|
|
123
|
+
webComponentProps['close-on-outside-click'] = 'false';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (closeOnEscape === false) { // Only set if explicitly false (default is true)
|
|
127
|
+
webComponentProps['close-on-escape'] = 'false';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (isProtected) {
|
|
131
|
+
webComponentProps.protected = ''; // Boolean attributes as empty string
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return React.createElement(
|
|
135
|
+
'ty-modal',
|
|
136
|
+
webComponentProps,
|
|
137
|
+
children
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
TyModal.displayName = 'TyModal';
|