tyrell-react 1.0.0-TC10

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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +410 -0
  3. package/dist/components/TyButton.d.ts +50 -0
  4. package/dist/components/TyButton.d.ts.map +1 -0
  5. package/dist/components/TyButton.js +68 -0
  6. package/dist/components/TyButton.js.map +1 -0
  7. package/dist/components/TyCalendar.d.ts +63 -0
  8. package/dist/components/TyCalendar.d.ts.map +1 -0
  9. package/dist/components/TyCalendar.js +122 -0
  10. package/dist/components/TyCalendar.js.map +1 -0
  11. package/dist/components/TyCalendarMonth.d.ts +32 -0
  12. package/dist/components/TyCalendarMonth.d.ts.map +1 -0
  13. package/dist/components/TyCalendarMonth.js +54 -0
  14. package/dist/components/TyCalendarMonth.js.map +1 -0
  15. package/dist/components/TyCalendarNavigation.d.ts +21 -0
  16. package/dist/components/TyCalendarNavigation.d.ts.map +1 -0
  17. package/dist/components/TyCalendarNavigation.js +50 -0
  18. package/dist/components/TyCalendarNavigation.js.map +1 -0
  19. package/dist/components/TyCheckbox.d.ts +39 -0
  20. package/dist/components/TyCheckbox.d.ts.map +1 -0
  21. package/dist/components/TyCheckbox.js +79 -0
  22. package/dist/components/TyCheckbox.js.map +1 -0
  23. package/dist/components/TyCopy.d.ts +21 -0
  24. package/dist/components/TyCopy.d.ts.map +1 -0
  25. package/dist/components/TyCopy.js +42 -0
  26. package/dist/components/TyCopy.js.map +1 -0
  27. package/dist/components/TyDatePicker.d.ts +45 -0
  28. package/dist/components/TyDatePicker.d.ts.map +1 -0
  29. package/dist/components/TyDatePicker.js +114 -0
  30. package/dist/components/TyDatePicker.js.map +1 -0
  31. package/dist/components/TyDropdown.d.ts +51 -0
  32. package/dist/components/TyDropdown.d.ts.map +1 -0
  33. package/dist/components/TyDropdown.js +109 -0
  34. package/dist/components/TyDropdown.js.map +1 -0
  35. package/dist/components/TyIcon.d.ts +17 -0
  36. package/dist/components/TyIcon.d.ts.map +1 -0
  37. package/dist/components/TyIcon.js +41 -0
  38. package/dist/components/TyIcon.js.map +1 -0
  39. package/dist/components/TyInput.d.ts +65 -0
  40. package/dist/components/TyInput.d.ts.map +1 -0
  41. package/dist/components/TyInput.js +105 -0
  42. package/dist/components/TyInput.js.map +1 -0
  43. package/dist/components/TyModal.d.ts +29 -0
  44. package/dist/components/TyModal.d.ts.map +1 -0
  45. package/dist/components/TyModal.js +74 -0
  46. package/dist/components/TyModal.js.map +1 -0
  47. package/dist/components/TyMultiselect.d.ts +51 -0
  48. package/dist/components/TyMultiselect.d.ts.map +1 -0
  49. package/dist/components/TyMultiselect.js +103 -0
  50. package/dist/components/TyMultiselect.js.map +1 -0
  51. package/dist/components/TyOption.d.ts +10 -0
  52. package/dist/components/TyOption.d.ts.map +1 -0
  53. package/dist/components/TyOption.js +25 -0
  54. package/dist/components/TyOption.js.map +1 -0
  55. package/dist/components/TyPopup.d.ts +24 -0
  56. package/dist/components/TyPopup.d.ts.map +1 -0
  57. package/dist/components/TyPopup.js +61 -0
  58. package/dist/components/TyPopup.js.map +1 -0
  59. package/dist/components/TyRadio.d.ts +20 -0
  60. package/dist/components/TyRadio.d.ts.map +1 -0
  61. package/dist/components/TyRadio.js +31 -0
  62. package/dist/components/TyRadio.js.map +1 -0
  63. package/dist/components/TyRadioGroup.d.ts +40 -0
  64. package/dist/components/TyRadioGroup.d.ts.map +1 -0
  65. package/dist/components/TyRadioGroup.js +58 -0
  66. package/dist/components/TyRadioGroup.js.map +1 -0
  67. package/dist/components/TyResizeObserver.d.ts +11 -0
  68. package/dist/components/TyResizeObserver.d.ts.map +1 -0
  69. package/dist/components/TyResizeObserver.js +28 -0
  70. package/dist/components/TyResizeObserver.js.map +1 -0
  71. package/dist/components/TyScrollContainer.d.ts +25 -0
  72. package/dist/components/TyScrollContainer.d.ts.map +1 -0
  73. package/dist/components/TyScrollContainer.js +43 -0
  74. package/dist/components/TyScrollContainer.js.map +1 -0
  75. package/dist/components/TyStep.d.ts +17 -0
  76. package/dist/components/TyStep.d.ts.map +1 -0
  77. package/dist/components/TyStep.js +35 -0
  78. package/dist/components/TyStep.js.map +1 -0
  79. package/dist/components/TySwitch.d.ts +35 -0
  80. package/dist/components/TySwitch.d.ts.map +1 -0
  81. package/dist/components/TySwitch.js +54 -0
  82. package/dist/components/TySwitch.js.map +1 -0
  83. package/dist/components/TyTab.d.ts +13 -0
  84. package/dist/components/TyTab.d.ts.map +1 -0
  85. package/dist/components/TyTab.js +32 -0
  86. package/dist/components/TyTab.js.map +1 -0
  87. package/dist/components/TyTabs.d.ts +23 -0
  88. package/dist/components/TyTabs.d.ts.map +1 -0
  89. package/dist/components/TyTabs.js +48 -0
  90. package/dist/components/TyTabs.js.map +1 -0
  91. package/dist/components/TyTag.d.ts +22 -0
  92. package/dist/components/TyTag.d.ts.map +1 -0
  93. package/dist/components/TyTag.js +45 -0
  94. package/dist/components/TyTag.js.map +1 -0
  95. package/dist/components/TyTextarea.d.ts +37 -0
  96. package/dist/components/TyTextarea.d.ts.map +1 -0
  97. package/dist/components/TyTextarea.js +93 -0
  98. package/dist/components/TyTextarea.js.map +1 -0
  99. package/dist/components/TyTooltip.d.ts +17 -0
  100. package/dist/components/TyTooltip.d.ts.map +1 -0
  101. package/dist/components/TyTooltip.js +40 -0
  102. package/dist/components/TyTooltip.js.map +1 -0
  103. package/dist/components/TyWizard.d.ts +26 -0
  104. package/dist/components/TyWizard.d.ts.map +1 -0
  105. package/dist/components/TyWizard.js +50 -0
  106. package/dist/components/TyWizard.js.map +1 -0
  107. package/dist/components/index.d.ts +105 -0
  108. package/dist/components/index.d.ts.map +1 -0
  109. package/dist/components/index.js +112 -0
  110. package/dist/components/index.js.map +1 -0
  111. package/package.json +46 -0
  112. package/src/components/EventConventionTest.tsx +155 -0
  113. package/src/components/TyButton.tsx +145 -0
  114. package/src/components/TyCalendar.tsx +244 -0
  115. package/src/components/TyCalendarMonth.tsx +108 -0
  116. package/src/components/TyCalendarNavigation.tsx +91 -0
  117. package/src/components/TyCheckbox.tsx +149 -0
  118. package/src/components/TyCopy.tsx +78 -0
  119. package/src/components/TyDatePicker.tsx +216 -0
  120. package/src/components/TyDropdown.tsx +218 -0
  121. package/src/components/TyIcon.tsx +72 -0
  122. package/src/components/TyInput.tsx +207 -0
  123. package/src/components/TyModal.tsx +142 -0
  124. package/src/components/TyMultiselect.tsx +200 -0
  125. package/src/components/TyOption.tsx +42 -0
  126. package/src/components/TyPopup.tsx +111 -0
  127. package/src/components/TyRadio.tsx +56 -0
  128. package/src/components/TyRadioGroup.tsx +121 -0
  129. package/src/components/TyResizeObserver.tsx +54 -0
  130. package/src/components/TyScrollContainer.tsx +87 -0
  131. package/src/components/TyStep.tsx +71 -0
  132. package/src/components/TySwitch.tsx +108 -0
  133. package/src/components/TyTab.tsx +63 -0
  134. package/src/components/TyTabs.tsx +93 -0
  135. package/src/components/TyTag.tsx +79 -0
  136. package/src/components/TyTextarea.tsx +154 -0
  137. package/src/components/TyTooltip.tsx +83 -0
  138. package/src/components/TyWizard.tsx +99 -0
  139. package/src/components/index.ts +251 -0
@@ -0,0 +1,216 @@
1
+ import React, { useEffect, useRef, useCallback } from 'react';
2
+
3
+ // Type definitions for Ty DatePicker component
4
+ export interface TyDatePickerEventDetail {
5
+ /** The selected date value (ISO string or formatted string based on format) */
6
+ value: string | null;
7
+ /** The selected date as milliseconds since epoch */
8
+ milliseconds: number | null;
9
+ /** Source of the change: "selection" | "time-change" | "clear" | "external" */
10
+ source: 'selection' | 'time-change' | 'clear' | 'external';
11
+ /** Formatted display value */
12
+ formatted: string | null;
13
+ }
14
+
15
+ export interface TyDatePickerProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {
16
+ /** The selected date value (ISO string or formatted string) */
17
+ value?: string;
18
+
19
+ /** Input size: "sm" | "md" | "lg" */
20
+ size?: 'sm' | 'md' | 'lg';
21
+
22
+ /** Visual flavor: "default" | "success" | "danger" | "warning" */
23
+ flavor?: 'default' | 'success' | 'danger' | 'warning';
24
+
25
+ /** Label text displayed above the input */
26
+ label?: string;
27
+
28
+ /** Placeholder text when no date is selected */
29
+ placeholder?: string;
30
+
31
+ /** Whether the field is required */
32
+ required?: boolean;
33
+
34
+ /** Whether the field is disabled */
35
+ disabled?: boolean;
36
+
37
+ /** Form field name for form submission */
38
+ name?: string;
39
+
40
+ /** Whether to show a clear button */
41
+ clearable?: boolean;
42
+
43
+ /** Date format: "short" | "medium" | "long" | "full" | custom format string */
44
+ format?: 'short' | 'medium' | 'long' | 'full' | string;
45
+
46
+ /** Locale for date formatting (e.g., "en-US", "de-DE") */
47
+ locale?: string;
48
+
49
+ /** Whether to include time selection */
50
+ withTime?: boolean;
51
+
52
+ /** Callback when the date value changes */
53
+ onChange?: (event: CustomEvent<TyDatePickerEventDetail>) => void;
54
+
55
+ /** Callback when the dropdown opens */
56
+ onOpen?: (event: CustomEvent<{}>) => void;
57
+
58
+ /** Callback when the dropdown closes */
59
+ onClose?: (event: CustomEvent<{}>) => void;
60
+ }
61
+
62
+ // React wrapper for ty-date-picker web component
63
+ export const TyDatePicker = React.forwardRef<HTMLElement, TyDatePickerProps>(
64
+ ({
65
+ value,
66
+ size,
67
+ flavor,
68
+ label,
69
+ placeholder,
70
+ required,
71
+ disabled,
72
+ name,
73
+ clearable,
74
+ format,
75
+ locale,
76
+ withTime,
77
+ onChange,
78
+ onOpen,
79
+ onClose,
80
+ ...props
81
+ }, ref) => {
82
+ const elementRef = useRef<HTMLElement>(null);
83
+
84
+ // Handle ref forwarding
85
+ useEffect(() => {
86
+ if (ref && elementRef.current) {
87
+ if (typeof ref === 'function') {
88
+ ref(elementRef.current);
89
+ } else {
90
+ ref.current = elementRef.current;
91
+ }
92
+ }
93
+ }, [ref]);
94
+
95
+ // Sync value property with the web component
96
+ useEffect(() => {
97
+ const element = elementRef.current;
98
+ if (element && value !== undefined) {
99
+ // Set the value property directly on the element
100
+ (element as any).value = value || '';
101
+ }
102
+ }, [value]);
103
+
104
+ // Handle change events
105
+ const handleChange = useCallback((event: Event) => {
106
+ const customEvent = event as CustomEvent<TyDatePickerEventDetail>;
107
+ if (onChange) {
108
+ onChange(customEvent);
109
+ }
110
+ }, [onChange]);
111
+
112
+ // Handle open events
113
+ const handleOpen = useCallback((event: Event) => {
114
+ const customEvent = event as CustomEvent<{}>;
115
+ if (onOpen) {
116
+ onOpen(customEvent);
117
+ }
118
+ }, [onOpen]);
119
+
120
+ // Handle close events
121
+ const handleClose = useCallback((event: Event) => {
122
+ const customEvent = event as CustomEvent<{}>;
123
+ if (onClose) {
124
+ onClose(customEvent);
125
+ }
126
+ }, [onClose]);
127
+
128
+ // Set up event listeners
129
+ useEffect(() => {
130
+ const element = elementRef.current;
131
+ if (!element) return;
132
+
133
+ const listeners: Array<[string, EventListener]> = [];
134
+
135
+ if (onChange) {
136
+ element.addEventListener('change', handleChange);
137
+ listeners.push(['change', handleChange]);
138
+ }
139
+
140
+ if (onOpen) {
141
+ element.addEventListener('open', handleOpen);
142
+ listeners.push(['open', handleOpen]);
143
+ }
144
+
145
+ if (onClose) {
146
+ element.addEventListener('close', handleClose);
147
+ listeners.push(['close', handleClose]);
148
+ }
149
+
150
+ return () => {
151
+ listeners.forEach(([eventName, handler]) => {
152
+ element.removeEventListener(eventName, handler);
153
+ });
154
+ };
155
+ }, [handleChange, handleOpen, handleClose, onChange, onOpen, onClose]);
156
+
157
+ // Convert React props to web component attributes
158
+ const webComponentProps: Record<string, any> = {
159
+ ...props,
160
+ ref: elementRef,
161
+ };
162
+
163
+ // Add optional attributes only if they have values
164
+ if (value !== undefined) {
165
+ webComponentProps.value = value;
166
+ }
167
+
168
+ if (size) {
169
+ webComponentProps.size = size;
170
+ }
171
+
172
+ if (flavor) {
173
+ webComponentProps.flavor = flavor;
174
+ }
175
+
176
+ if (label) {
177
+ webComponentProps.label = label;
178
+ }
179
+
180
+ if (placeholder) {
181
+ webComponentProps.placeholder = placeholder;
182
+ }
183
+
184
+ if (required) {
185
+ webComponentProps.required = ''; // Boolean attributes as empty string
186
+ }
187
+
188
+ if (disabled) {
189
+ webComponentProps.disabled = ''; // Boolean attributes as empty string
190
+ }
191
+
192
+ if (name) {
193
+ webComponentProps.name = name;
194
+ }
195
+
196
+ if (clearable) {
197
+ webComponentProps.clearable = ''; // Boolean attributes as empty string
198
+ }
199
+
200
+ if (format) {
201
+ webComponentProps.format = format;
202
+ }
203
+
204
+ if (locale) {
205
+ webComponentProps.locale = locale;
206
+ }
207
+
208
+ if (withTime) {
209
+ webComponentProps['with-time'] = ''; // Convert camelCase to kebab-case
210
+ }
211
+
212
+ return React.createElement('ty-date-picker', webComponentProps);
213
+ }
214
+ );
215
+
216
+ TyDatePicker.displayName = 'TyDatePicker';
@@ -0,0 +1,218 @@
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
+ value,
89
+ ...props
90
+ }, ref) => {
91
+ const elementRef = useRef<HTMLElement>(null);
92
+
93
+ const handleChange = useCallback((event: CustomEvent<TyDropdownEventDetail>) => {
94
+ if (onChange) {
95
+ onChange(event);
96
+ }
97
+ }, [onChange]);
98
+
99
+ const handleSearch = useCallback((event: CustomEvent<{ query: string; element: HTMLElement }>) => {
100
+ if (onSearch) {
101
+ onSearch(event);
102
+ }
103
+ }, [onSearch]);
104
+
105
+ useEffect(() => {
106
+ const element = elementRef.current;
107
+ if (!element) return;
108
+
109
+ // Listen for custom events from ty-dropdown
110
+ if (onChange) {
111
+ element.addEventListener('change', handleChange as EventListener);
112
+ }
113
+
114
+ if (onSearch) {
115
+ element.addEventListener('search', handleSearch as EventListener);
116
+ }
117
+
118
+ return () => {
119
+ if (onChange) {
120
+ element.removeEventListener('change', handleChange as EventListener);
121
+ }
122
+ if (onSearch) {
123
+ element.removeEventListener('search', handleSearch as EventListener);
124
+ }
125
+ };
126
+ }, [handleChange, handleSearch, onChange, onSearch]);
127
+
128
+ // Imperatively sync `value` to the underlying element's property whenever
129
+ // it changes. React 18's prop-to-property bridging for custom elements is
130
+ // unreliable for empty strings (programmatic resets), so we set the
131
+ // property directly to guarantee the dropdown clears on `value=""`.
132
+ useEffect(() => {
133
+ const element = elementRef.current as any;
134
+ if (!element) return;
135
+ if (element.value !== value) {
136
+ element.value = value ?? '';
137
+ }
138
+ }, [value]);
139
+
140
+ // Handle ref forwarding
141
+ useEffect(() => {
142
+ if (ref && elementRef.current) {
143
+ if (typeof ref === 'function') {
144
+ ref(elementRef.current);
145
+ } else {
146
+ ref.current = elementRef.current;
147
+ }
148
+ }
149
+ }, [ref]);
150
+
151
+ // Render options from data if provided, otherwise use children
152
+ const renderContent = () => {
153
+ if (options && options.length > 0) {
154
+ // Data-driven approach - create ty-option elements
155
+ return options.map((option, index) =>
156
+ React.createElement(
157
+ 'ty-option',
158
+ {
159
+ key: option.value || index,
160
+ value: option.value,
161
+ ...(option.disabled && { disabled: "" }),
162
+ },
163
+ option.text
164
+ )
165
+ );
166
+ }
167
+
168
+ // Slotted approach - use provided children
169
+ return children;
170
+ };
171
+
172
+ // Convert React props to web component attributes
173
+ const webComponentProps: Record<string, any> = {
174
+ ...props,
175
+ ref: elementRef,
176
+ };
177
+
178
+ // Add conditional attributes
179
+ if (disabled) webComponentProps.disabled = '';
180
+
181
+ // Handle search functionality (prefer not-searchable over deprecated externalSearch)
182
+ if (notSearchable) {
183
+ webComponentProps['not-searchable'] = '';
184
+ } else if (externalSearch) {
185
+ // Support deprecated externalSearch for backward compatibility
186
+ webComponentProps['not-searchable'] = '';
187
+ }
188
+
189
+ // Handle clearable functionality
190
+ if (clearable !== undefined) {
191
+ if (clearable) {
192
+ webComponentProps.clearable = '';
193
+ } else {
194
+ webComponentProps['not-clearable'] = '';
195
+ }
196
+ }
197
+
198
+ if (notClearable) {
199
+ webComponentProps['not-clearable'] = '';
200
+ }
201
+
202
+ // Add debounce attribute
203
+ if (debounce !== undefined) {
204
+ webComponentProps.debounce = debounce;
205
+ }
206
+
207
+ // Add string attributes
208
+ if (name) webComponentProps.name = name;
209
+
210
+ return React.createElement(
211
+ 'ty-dropdown',
212
+ webComponentProps,
213
+ renderContent()
214
+ );
215
+ }
216
+ );
217
+
218
+ 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,207 @@
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
+ // Imperatively sync `value` to the underlying element's property whenever
172
+ // the React prop changes. React 18's prop-to-property bridging for custom
173
+ // elements is unreliable for empty strings, so we set the property directly
174
+ // to guarantee resets (`value=""`) clear the visible content.
175
+ useEffect(() => {
176
+ const element = elementRef.current as any;
177
+ if (!element) return;
178
+ const next = (props as any).value ?? '';
179
+ if (element.value !== next) {
180
+ element.value = next;
181
+ }
182
+ }, [(props as any).value]);
183
+
184
+ // Convert React props to web component attributes
185
+ const webComponentProps: Record<string, any> = {
186
+ ...props,
187
+ ref: elementRef,
188
+ };
189
+
190
+ // Add conditional attributes
191
+ if (disabled) webComponentProps.disabled = '';
192
+ if (checked) webComponentProps.checked = '';
193
+
194
+ // Add string attributes
195
+ if (name) webComponentProps.name = name;
196
+
197
+ // Add debounce attribute
198
+ if (debounce !== undefined) webComponentProps.debounce = debounce;
199
+
200
+ return React.createElement(
201
+ 'ty-input',
202
+ webComponentProps
203
+ );
204
+ }
205
+ );
206
+
207
+ TyInput.displayName = 'TyInput';