tyrell-react 1.0.0-RC10

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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +410 -0
  3. package/dist/components/TyButton.d.ts +52 -0
  4. package/dist/components/TyButton.d.ts.map +1 -0
  5. package/dist/components/TyButton.js +76 -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 +128 -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 +76 -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 +46 -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 +122 -0
  30. package/dist/components/TyDatePicker.js.map +1 -0
  31. package/dist/components/TyDropdown.d.ts +62 -0
  32. package/dist/components/TyDropdown.d.ts.map +1 -0
  33. package/dist/components/TyDropdown.js +124 -0
  34. package/dist/components/TyDropdown.js.map +1 -0
  35. package/dist/components/TyFileUpload.d.ts +31 -0
  36. package/dist/components/TyFileUpload.d.ts.map +1 -0
  37. package/dist/components/TyFileUpload.js +56 -0
  38. package/dist/components/TyFileUpload.js.map +1 -0
  39. package/dist/components/TyIcon.d.ts +17 -0
  40. package/dist/components/TyIcon.d.ts.map +1 -0
  41. package/dist/components/TyIcon.js +42 -0
  42. package/dist/components/TyIcon.js.map +1 -0
  43. package/dist/components/TyInput.d.ts +65 -0
  44. package/dist/components/TyInput.d.ts.map +1 -0
  45. package/dist/components/TyInput.js +134 -0
  46. package/dist/components/TyInput.js.map +1 -0
  47. package/dist/components/TyModal.d.ts +48 -0
  48. package/dist/components/TyModal.d.ts.map +1 -0
  49. package/dist/components/TyModal.js +120 -0
  50. package/dist/components/TyModal.js.map +1 -0
  51. package/dist/components/TyMultiselect.d.ts +57 -0
  52. package/dist/components/TyMultiselect.d.ts.map +1 -0
  53. package/dist/components/TyMultiselect.js +111 -0
  54. package/dist/components/TyMultiselect.js.map +1 -0
  55. package/dist/components/TyOption.d.ts +10 -0
  56. package/dist/components/TyOption.d.ts.map +1 -0
  57. package/dist/components/TyOption.js +29 -0
  58. package/dist/components/TyOption.js.map +1 -0
  59. package/dist/components/TyPopup.d.ts +24 -0
  60. package/dist/components/TyPopup.d.ts.map +1 -0
  61. package/dist/components/TyPopup.js +70 -0
  62. package/dist/components/TyPopup.js.map +1 -0
  63. package/dist/components/TyRadio.d.ts +20 -0
  64. package/dist/components/TyRadio.d.ts.map +1 -0
  65. package/dist/components/TyRadio.js +35 -0
  66. package/dist/components/TyRadio.js.map +1 -0
  67. package/dist/components/TyRadioGroup.d.ts +40 -0
  68. package/dist/components/TyRadioGroup.d.ts.map +1 -0
  69. package/dist/components/TyRadioGroup.js +61 -0
  70. package/dist/components/TyRadioGroup.js.map +1 -0
  71. package/dist/components/TyResizeObserver.d.ts +11 -0
  72. package/dist/components/TyResizeObserver.d.ts.map +1 -0
  73. package/dist/components/TyResizeObserver.js +28 -0
  74. package/dist/components/TyResizeObserver.js.map +1 -0
  75. package/dist/components/TyScrollContainer.d.ts +25 -0
  76. package/dist/components/TyScrollContainer.d.ts.map +1 -0
  77. package/dist/components/TyScrollContainer.js +61 -0
  78. package/dist/components/TyScrollContainer.js.map +1 -0
  79. package/dist/components/TyStep.d.ts +17 -0
  80. package/dist/components/TyStep.d.ts.map +1 -0
  81. package/dist/components/TyStep.js +35 -0
  82. package/dist/components/TyStep.js.map +1 -0
  83. package/dist/components/TySwitch.d.ts +35 -0
  84. package/dist/components/TySwitch.d.ts.map +1 -0
  85. package/dist/components/TySwitch.js +59 -0
  86. package/dist/components/TySwitch.js.map +1 -0
  87. package/dist/components/TyTab.d.ts +13 -0
  88. package/dist/components/TyTab.d.ts.map +1 -0
  89. package/dist/components/TyTab.js +34 -0
  90. package/dist/components/TyTab.js.map +1 -0
  91. package/dist/components/TyTabs.d.ts +23 -0
  92. package/dist/components/TyTabs.d.ts.map +1 -0
  93. package/dist/components/TyTabs.js +48 -0
  94. package/dist/components/TyTabs.js.map +1 -0
  95. package/dist/components/TyTag.d.ts +22 -0
  96. package/dist/components/TyTag.d.ts.map +1 -0
  97. package/dist/components/TyTag.js +51 -0
  98. package/dist/components/TyTag.js.map +1 -0
  99. package/dist/components/TyTextarea.d.ts +37 -0
  100. package/dist/components/TyTextarea.d.ts.map +1 -0
  101. package/dist/components/TyTextarea.js +116 -0
  102. package/dist/components/TyTextarea.js.map +1 -0
  103. package/dist/components/TyTooltip.d.ts +17 -0
  104. package/dist/components/TyTooltip.d.ts.map +1 -0
  105. package/dist/components/TyTooltip.js +41 -0
  106. package/dist/components/TyTooltip.js.map +1 -0
  107. package/dist/components/TyWizard.d.ts +26 -0
  108. package/dist/components/TyWizard.d.ts.map +1 -0
  109. package/dist/components/TyWizard.js +50 -0
  110. package/dist/components/TyWizard.js.map +1 -0
  111. package/dist/components/index.d.ts +112 -0
  112. package/dist/components/index.d.ts.map +1 -0
  113. package/dist/components/index.js +127 -0
  114. package/dist/components/index.js.map +1 -0
  115. package/dist/utils/react-version.d.ts +2 -0
  116. package/dist/utils/react-version.d.ts.map +1 -0
  117. package/dist/utils/react-version.js +8 -0
  118. package/dist/utils/react-version.js.map +1 -0
  119. package/dist/utils/use-boolean-prop.d.ts +36 -0
  120. package/dist/utils/use-boolean-prop.d.ts.map +1 -0
  121. package/dist/utils/use-boolean-prop.js +62 -0
  122. package/dist/utils/use-boolean-prop.js.map +1 -0
  123. package/dist/version.d.ts +3 -0
  124. package/dist/version.d.ts.map +1 -0
  125. package/dist/version.js +6 -0
  126. package/dist/version.js.map +1 -0
  127. package/package.json +47 -0
  128. package/src/components/EventConventionTest.tsx +155 -0
  129. package/src/components/TyButton.tsx +157 -0
  130. package/src/components/TyCalendar.tsx +247 -0
  131. package/src/components/TyCalendarMonth.tsx +108 -0
  132. package/src/components/TyCalendarNavigation.tsx +91 -0
  133. package/src/components/TyCheckbox.tsx +147 -0
  134. package/src/components/TyCopy.tsx +83 -0
  135. package/src/components/TyDatePicker.tsx +215 -0
  136. package/src/components/TyDropdown.tsx +240 -0
  137. package/src/components/TyFileUpload.tsx +108 -0
  138. package/src/components/TyIcon.tsx +71 -0
  139. package/src/components/TyInput.tsx +239 -0
  140. package/src/components/TyModal.tsx +195 -0
  141. package/src/components/TyMultiselect.tsx +208 -0
  142. package/src/components/TyOption.tsx +47 -0
  143. package/src/components/TyPopup.tsx +116 -0
  144. package/src/components/TyRadio.tsx +61 -0
  145. package/src/components/TyRadioGroup.tsx +125 -0
  146. package/src/components/TyResizeObserver.tsx +54 -0
  147. package/src/components/TyScrollContainer.tsx +102 -0
  148. package/src/components/TyStep.tsx +71 -0
  149. package/src/components/TySwitch.tsx +114 -0
  150. package/src/components/TyTab.tsx +65 -0
  151. package/src/components/TyTabs.tsx +93 -0
  152. package/src/components/TyTag.tsx +86 -0
  153. package/src/components/TyTextarea.tsx +181 -0
  154. package/src/components/TyTooltip.tsx +83 -0
  155. package/src/components/TyWizard.tsx +99 -0
  156. package/src/components/index.ts +279 -0
  157. package/src/utils/react-version.ts +8 -0
  158. package/src/utils/use-boolean-prop.ts +62 -0
  159. package/src/version.ts +6 -0
@@ -0,0 +1,215 @@
1
+ import React, { useEffect, useRef, useCallback } from 'react';
2
+ import { needsPropertyBridge } from '../utils/react-version';
3
+ import { useBooleanProperty } from '../utils/use-boolean-prop';
4
+
5
+ // Type definitions for Ty DatePicker component
6
+ export interface TyDatePickerEventDetail {
7
+ /** The selected date value (ISO string or formatted string based on format) */
8
+ value: string | null;
9
+ /** The selected date as milliseconds since epoch */
10
+ milliseconds: number | null;
11
+ /** Source of the change: "selection" | "time-change" | "clear" | "external" */
12
+ source: 'selection' | 'time-change' | 'clear' | 'external';
13
+ /** Formatted display value */
14
+ formatted: string | null;
15
+ }
16
+
17
+ export interface TyDatePickerProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {
18
+ /** The selected date value (ISO string or formatted string) */
19
+ value?: string;
20
+
21
+ /** Input size: "sm" | "md" | "lg" */
22
+ size?: 'sm' | 'md' | 'lg';
23
+
24
+ /** Visual flavor: "default" | "success" | "danger" | "warning" */
25
+ flavor?: 'default' | 'success' | 'danger' | 'warning';
26
+
27
+ /** Label text displayed above the input */
28
+ label?: string;
29
+
30
+ /** Placeholder text when no date is selected */
31
+ placeholder?: string;
32
+
33
+ /** Whether the field is required */
34
+ required?: boolean;
35
+
36
+ /** Whether the field is disabled */
37
+ disabled?: boolean;
38
+
39
+ /** Form field name for form submission */
40
+ name?: string;
41
+
42
+ /** Whether to show a clear button */
43
+ clearable?: boolean;
44
+
45
+ /** Date format: "short" | "medium" | "long" | "full" | custom format string */
46
+ format?: 'short' | 'medium' | 'long' | 'full' | string;
47
+
48
+ /** Locale for date formatting (e.g., "en-US", "de-DE") */
49
+ locale?: string;
50
+
51
+ /** Whether to include time selection */
52
+ withTime?: boolean;
53
+
54
+ /** Callback when the date value changes */
55
+ onChange?: (event: CustomEvent<TyDatePickerEventDetail>) => void;
56
+
57
+ /** Callback when the dropdown opens */
58
+ onOpen?: (event: CustomEvent<{}>) => void;
59
+
60
+ /** Callback when the dropdown closes */
61
+ onClose?: (event: CustomEvent<{}>) => void;
62
+ }
63
+
64
+ // React wrapper for ty-date-picker web component
65
+ export const TyDatePicker = React.forwardRef<HTMLElement, TyDatePickerProps>(
66
+ ({
67
+ value,
68
+ size,
69
+ flavor,
70
+ label,
71
+ placeholder,
72
+ required,
73
+ disabled,
74
+ name,
75
+ clearable,
76
+ format,
77
+ locale,
78
+ withTime,
79
+ onChange,
80
+ onOpen,
81
+ onClose,
82
+ ...props
83
+ }, ref) => {
84
+ const elementRef = useRef<HTMLElement>(null);
85
+
86
+ // Handle ref forwarding
87
+ useEffect(() => {
88
+ if (ref && elementRef.current) {
89
+ if (typeof ref === 'function') {
90
+ ref(elementRef.current);
91
+ } else {
92
+ ref.current = elementRef.current;
93
+ }
94
+ }
95
+ }, [ref]);
96
+
97
+ // Sync value property with the web component.
98
+ // React 18 workaround: prop-to-property bridging is unreliable for empty
99
+ // strings on custom elements. React 19+ handles this natively.
100
+ useEffect(() => {
101
+ if (!needsPropertyBridge) return;
102
+ const element = elementRef.current;
103
+ if (element && value !== undefined) {
104
+ // Set the value property directly on the element
105
+ (element as any).value = value || '';
106
+ }
107
+ }, [value]);
108
+
109
+ // Handle change events
110
+ const handleChange = useCallback((event: Event) => {
111
+ const customEvent = event as CustomEvent<TyDatePickerEventDetail>;
112
+ if (onChange) {
113
+ onChange(customEvent);
114
+ }
115
+ }, [onChange]);
116
+
117
+ // Handle open/close events. Guard with `event.target === element` so
118
+ // bubbled open/close events from popup-like descendants slotted inside
119
+ // (rare for a date-picker, but defensive — same fix as TyModal/TyPopup)
120
+ // don't fire the consumer's onOpen/onClose.
121
+ const handleOpen = useCallback((event: Event) => {
122
+ if (event.target !== elementRef.current) return;
123
+ if (onOpen) onOpen(event as CustomEvent<{}>);
124
+ }, [onOpen]);
125
+
126
+ const handleClose = useCallback((event: Event) => {
127
+ if (event.target !== elementRef.current) return;
128
+ if (onClose) onClose(event as CustomEvent<{}>);
129
+ }, [onClose]);
130
+
131
+ // Set up event listeners
132
+ useEffect(() => {
133
+ const element = elementRef.current;
134
+ if (!element) return;
135
+
136
+ const listeners: Array<[string, EventListener]> = [];
137
+
138
+ if (onChange) {
139
+ element.addEventListener('change', handleChange);
140
+ listeners.push(['change', handleChange]);
141
+ }
142
+
143
+ if (onOpen) {
144
+ element.addEventListener('open', handleOpen);
145
+ listeners.push(['open', handleOpen]);
146
+ }
147
+
148
+ if (onClose) {
149
+ element.addEventListener('close', handleClose);
150
+ listeners.push(['close', handleClose]);
151
+ }
152
+
153
+ return () => {
154
+ listeners.forEach(([eventName, handler]) => {
155
+ element.removeEventListener(eventName, handler);
156
+ });
157
+ };
158
+ }, [handleChange, handleOpen, handleClose, onChange, onOpen, onClose]);
159
+
160
+ // Convert React props to web component attributes
161
+ const webComponentProps: Record<string, any> = {
162
+ ...props,
163
+ ref: elementRef,
164
+ };
165
+
166
+ // Add optional attributes only if they have values
167
+ if (value !== undefined) {
168
+ webComponentProps.value = value;
169
+ }
170
+
171
+ if (size) {
172
+ webComponentProps.size = size;
173
+ }
174
+
175
+ if (flavor) {
176
+ webComponentProps.flavor = flavor;
177
+ }
178
+
179
+ if (label) {
180
+ webComponentProps.label = label;
181
+ }
182
+
183
+ if (placeholder) {
184
+ webComponentProps.placeholder = placeholder;
185
+ }
186
+
187
+ const isRequired = useBooleanProperty(elementRef, 'required', required);
188
+ const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
189
+ const isClearable = useBooleanProperty(elementRef, 'clearable', clearable);
190
+
191
+ if (isRequired) webComponentProps.required = '';
192
+ if (isDisabled) webComponentProps.disabled = '';
193
+ if (isClearable) webComponentProps.clearable = '';
194
+
195
+ if (name) {
196
+ webComponentProps.name = name;
197
+ }
198
+
199
+ if (format) {
200
+ webComponentProps.format = format;
201
+ }
202
+
203
+ if (locale) {
204
+ webComponentProps.locale = locale;
205
+ }
206
+
207
+ if (withTime) {
208
+ webComponentProps['with-time'] = ''; // Convert camelCase to kebab-case
209
+ }
210
+
211
+ return React.createElement('ty-date-picker', webComponentProps);
212
+ }
213
+ );
214
+
215
+ TyDatePicker.displayName = 'TyDatePicker';
@@ -0,0 +1,240 @@
1
+ import React, { useEffect, useRef, useCallback } from 'react';
2
+ import { needsPropertyBridge } from '../utils/react-version';
3
+ import { useBooleanProperty, coerceBool } from '../utils/use-boolean-prop';
4
+
5
+ // Option data structure for data-driven approach
6
+ export interface OptionData {
7
+ value: string;
8
+ text: string;
9
+ disabled?: boolean;
10
+ }
11
+
12
+ // Event detail structure for dropdown events
13
+ export interface TyDropdownEventDetail {
14
+ option: HTMLElement;
15
+ }
16
+
17
+ // Type definitions for Ty Dropdown component
18
+ export interface TyDropdownProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'style'> {
19
+ style?: import('./TyInput').TyInputCSSProperties;
20
+ /** Semantic styling variant */
21
+ flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
22
+
23
+ /** Dropdown size */
24
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
25
+
26
+ /** Selected value */
27
+ value?: string;
28
+
29
+ /** Placeholder text */
30
+ placeholder?: string;
31
+
32
+ /** Dropdown label */
33
+ label?: string;
34
+
35
+ /** Disable the dropdown */
36
+ disabled?: boolean;
37
+
38
+ /**
39
+ * Loading state — replaces the open popup options list with a centered
40
+ * spinner. Search input stays usable. Pair with `externalSearch` while
41
+ * fetching results from a parent-owned data source.
42
+ */
43
+ loading?: boolean;
44
+
45
+ /** Make dropdown readonly */
46
+ readonly?: boolean;
47
+
48
+ /** Required field */
49
+ required?: boolean;
50
+
51
+ /** Show clear button */
52
+ clearable?: boolean;
53
+
54
+ /** Disable clear button (alias for clearable={false}) */
55
+ notClearable?: boolean;
56
+
57
+ /** Debounce in milliseconds (0-5000) */
58
+ debounce?: number;
59
+
60
+ /**
61
+ * Switch to external (remote) search mode. Default is `false` — the dropdown
62
+ * filters its options locally. When `true`, the dropdown stops filtering and
63
+ * dispatches `search` events on each keystroke; the parent owns filtering
64
+ * and updates the children.
65
+ */
66
+ externalSearch?: boolean;
67
+
68
+ /** @deprecated Use `externalSearch` instead. */
69
+ notSearchable?: boolean;
70
+
71
+ /** @deprecated Use `externalSearch` instead. Pass `searchable={false}` was equivalent to `externalSearch={true}`. */
72
+ searchable?: boolean;
73
+
74
+ /** Form field name for form submission */
75
+ name?: string;
76
+
77
+ // Data-driven approach
78
+ options?: OptionData[];
79
+
80
+ // React event handlers
81
+ onChange?: (event: CustomEvent<TyDropdownEventDetail>) => void;
82
+ onSearch?: (event: CustomEvent<{ query: string; element: HTMLElement }>) => void;
83
+
84
+ // Children for slotted approach
85
+ children?: React.ReactNode;
86
+ }
87
+
88
+ // React wrapper for ty-dropdown web component
89
+ export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
90
+ ({
91
+ options,
92
+ children,
93
+ onChange,
94
+ onSearch,
95
+ disabled,
96
+ loading,
97
+ externalSearch,
98
+ notSearchable,
99
+ searchable,
100
+ clearable,
101
+ notClearable,
102
+ debounce,
103
+ name,
104
+ value,
105
+ ...props
106
+ }, ref) => {
107
+ const elementRef = useRef<HTMLElement>(null);
108
+
109
+ const handleChange = useCallback((event: CustomEvent<TyDropdownEventDetail>) => {
110
+ if (onChange) {
111
+ onChange(event);
112
+ }
113
+ }, [onChange]);
114
+
115
+ const handleSearch = useCallback((event: CustomEvent<{ query: string; element: HTMLElement }>) => {
116
+ if (onSearch) {
117
+ onSearch(event);
118
+ }
119
+ }, [onSearch]);
120
+
121
+ useEffect(() => {
122
+ const element = elementRef.current;
123
+ if (!element) return;
124
+
125
+ // Listen for custom events from ty-dropdown
126
+ if (onChange) {
127
+ element.addEventListener('change', handleChange as EventListener);
128
+ }
129
+
130
+ if (onSearch) {
131
+ element.addEventListener('search', handleSearch as EventListener);
132
+ }
133
+
134
+ return () => {
135
+ if (onChange) {
136
+ element.removeEventListener('change', handleChange as EventListener);
137
+ }
138
+ if (onSearch) {
139
+ element.removeEventListener('search', handleSearch as EventListener);
140
+ }
141
+ };
142
+ }, [handleChange, handleSearch, onChange, onSearch]);
143
+
144
+ // Imperatively sync `value` to the underlying element's property whenever
145
+ // it changes. React 18's prop-to-property bridging for custom elements is
146
+ // unreliable for empty strings (programmatic resets), so we set the
147
+ // property directly to guarantee the dropdown clears on `value=""`.
148
+ // React 19+ handles this natively, so the effect short-circuits there.
149
+ useEffect(() => {
150
+ if (!needsPropertyBridge) return;
151
+ const element = elementRef.current as any;
152
+ if (!element) return;
153
+ if (element.value !== value) {
154
+ element.value = value ?? '';
155
+ }
156
+ }, [value]);
157
+
158
+ // Handle ref forwarding
159
+ useEffect(() => {
160
+ if (ref && elementRef.current) {
161
+ if (typeof ref === 'function') {
162
+ ref(elementRef.current);
163
+ } else {
164
+ ref.current = elementRef.current;
165
+ }
166
+ }
167
+ }, [ref]);
168
+
169
+ // Render options from data if provided, otherwise use children
170
+ const renderContent = () => {
171
+ if (options && options.length > 0) {
172
+ // Data-driven approach - create ty-option elements
173
+ return options.map((option, index) =>
174
+ React.createElement(
175
+ 'ty-option',
176
+ {
177
+ key: option.value || index,
178
+ value: option.value,
179
+ ...(option.disabled && { disabled: "" }),
180
+ },
181
+ option.text
182
+ )
183
+ );
184
+ }
185
+
186
+ // Slotted approach - use provided children
187
+ return children;
188
+ };
189
+
190
+ // Imperative property sync for boolean props (see use-boolean-prop.ts).
191
+ const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
192
+ const isLoading = useBooleanProperty(elementRef, 'loading', loading);
193
+ const isExternalSearch = coerceBool(externalSearch) || coerceBool(notSearchable) || searchable === false;
194
+ useBooleanProperty(elementRef, 'externalSearch', isExternalSearch);
195
+ // clearable: explicit boolean OR the `notClearable` alias inverts it
196
+ const isClearable = clearable !== undefined
197
+ ? coerceBool(clearable)
198
+ : (coerceBool(notClearable) ? false : undefined);
199
+ useEffect(() => {
200
+ if (!needsPropertyBridge) return;
201
+ if (isClearable === undefined) return;
202
+ const el = elementRef.current as any;
203
+ if (!el) return;
204
+ if (Boolean(el.clearable) !== isClearable) el.clearable = isClearable;
205
+ }, [isClearable]);
206
+
207
+ // Convert React props to web component attributes
208
+ const webComponentProps: Record<string, any> = {
209
+ ...props,
210
+ ref: elementRef,
211
+ };
212
+
213
+ if (isDisabled) webComponentProps.disabled = '';
214
+ if (isLoading) webComponentProps.loading = '';
215
+ if (isExternalSearch) webComponentProps['external-search'] = '';
216
+
217
+ // Handle clearable functionality
218
+ if (isClearable === true) {
219
+ webComponentProps.clearable = '';
220
+ } else if (isClearable === false) {
221
+ webComponentProps['not-clearable'] = '';
222
+ }
223
+
224
+ // Add debounce attribute
225
+ if (debounce !== undefined) {
226
+ webComponentProps.debounce = debounce;
227
+ }
228
+
229
+ // Add string attributes
230
+ if (name) webComponentProps.name = name;
231
+
232
+ return React.createElement(
233
+ 'ty-dropdown',
234
+ webComponentProps,
235
+ renderContent()
236
+ );
237
+ }
238
+ );
239
+
240
+ TyDropdown.displayName = 'TyDropdown';
@@ -0,0 +1,108 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { useBooleanProperty } from '../utils/use-boolean-prop';
3
+
4
+ // Type definitions for Ty File Upload component
5
+ export interface TyFileUploadProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {
6
+ /** Form field name — used as the FormData key */
7
+ name?: string;
8
+
9
+ /** Allow selecting multiple files */
10
+ multiple?: boolean;
11
+
12
+ /** File type filter passed to the underlying input (e.g. "image/*", ".pdf,.docx") */
13
+ accept?: string;
14
+
15
+ /** Field label rendered above the drop zone */
16
+ label?: string;
17
+
18
+ /** Hint text shown inside the drop zone when no files are selected */
19
+ placeholder?: string;
20
+
21
+ /** Disable interaction */
22
+ disabled?: boolean;
23
+
24
+ /** Mark the field as required (shows asterisk) */
25
+ required?: boolean;
26
+
27
+ /** Validation error message — also applies danger border styling */
28
+ error?: string;
29
+
30
+ /**
31
+ * Fires when the selection changes — browse, drag-drop, or remove.
32
+ * Maps to the native 'change' event from ty-file-upload.
33
+ */
34
+ onChange?: (event: CustomEvent<TyFileUploadEventDetail>) => void;
35
+ }
36
+
37
+ export interface TyFileUploadEventDetail {
38
+ value: File[];
39
+ files: File[];
40
+ names: string[];
41
+ }
42
+
43
+ // React wrapper for ty-file-upload web component
44
+ export const TyFileUpload = React.forwardRef<HTMLElement, TyFileUploadProps>(
45
+ ({
46
+ name,
47
+ multiple,
48
+ accept,
49
+ label,
50
+ placeholder,
51
+ disabled,
52
+ required,
53
+ error,
54
+ onChange,
55
+ ...props
56
+ }, ref) => {
57
+ const elementRef = useRef<HTMLElement>(null);
58
+
59
+ useEffect(() => {
60
+ const element = elementRef.current;
61
+ if (!element) return;
62
+
63
+ const handleChange = (event: Event) => {
64
+ if (onChange) {
65
+ onChange(event as CustomEvent<TyFileUploadEventDetail>);
66
+ }
67
+ };
68
+
69
+ element.addEventListener('change', handleChange);
70
+ return () => {
71
+ element.removeEventListener('change', handleChange);
72
+ };
73
+ }, [onChange]);
74
+
75
+ useEffect(() => {
76
+ if (ref && elementRef.current) {
77
+ if (typeof ref === 'function') {
78
+ ref(elementRef.current);
79
+ } else {
80
+ ref.current = elementRef.current;
81
+ }
82
+ }
83
+ }, [ref]);
84
+
85
+ const isMultiple = useBooleanProperty(elementRef, 'multiple', multiple);
86
+ const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
87
+ const isRequired = useBooleanProperty(elementRef, 'required', required);
88
+
89
+ const webComponentProps: Record<string, any> = {
90
+ ...props,
91
+ ref: elementRef,
92
+ };
93
+
94
+ if (isMultiple) webComponentProps.multiple = '';
95
+ if (isDisabled) webComponentProps.disabled = '';
96
+ if (isRequired) webComponentProps.required = '';
97
+
98
+ if (name) webComponentProps.name = name;
99
+ if (accept) webComponentProps.accept = accept;
100
+ if (label) webComponentProps.label = label;
101
+ if (placeholder) webComponentProps.placeholder = placeholder;
102
+ if (error) webComponentProps.error = error;
103
+
104
+ return React.createElement('ty-file-upload', webComponentProps);
105
+ }
106
+ );
107
+
108
+ TyFileUpload.displayName = 'TyFileUpload';
@@ -0,0 +1,71 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { useBooleanProperty } from '../utils/use-boolean-prop';
3
+
4
+ // Type definitions for Ty Icon component
5
+ export interface TyIconProps extends React.HTMLAttributes<HTMLElement> {
6
+ /** Icon name from the icon registry (e.g., 'home', 'star', 'settings') */
7
+ name: string;
8
+
9
+ /** Icon size - relative (em-based) or absolute (pixel-based) */
10
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '12' | '14' | '16' | '18' | '20' | '24' | '32' | '48';
11
+
12
+ /** Enable spinning animation */
13
+ spin?: boolean;
14
+
15
+ /** Enable pulse animation */
16
+ pulse?: boolean;
17
+
18
+ /** Animation tempo/speed */
19
+ tempo?: 'slow' | 'fast';
20
+
21
+ /** Additional CSS classes */
22
+ className?: string;
23
+ }
24
+
25
+ // React wrapper for ty-icon web component
26
+ export const TyIcon = React.forwardRef<HTMLElement, TyIconProps>(
27
+ ({ name, size, spin, pulse, tempo, className, ...props }, ref) => {
28
+ const elementRef = useRef<HTMLElement>(null);
29
+
30
+ // Handle ref forwarding
31
+ useEffect(() => {
32
+ if (ref && elementRef.current) {
33
+ if (typeof ref === 'function') {
34
+ ref(elementRef.current);
35
+ } else {
36
+ ref.current = elementRef.current;
37
+ }
38
+ }
39
+ }, [ref]);
40
+
41
+ const isSpin = useBooleanProperty(elementRef, 'spin', spin);
42
+ const isPulse = useBooleanProperty(elementRef, 'pulse', pulse);
43
+
44
+ // Convert React props to web component attributes
45
+ const webComponentProps: Record<string, any> = {
46
+ ...props,
47
+ name,
48
+ ref: elementRef,
49
+ };
50
+
51
+ // Add optional attributes only if they have values
52
+ if (size) {
53
+ webComponentProps.size = size;
54
+ }
55
+
56
+ if (isSpin) webComponentProps.spin = '';
57
+ if (isPulse) webComponentProps.pulse = '';
58
+
59
+ if (tempo) {
60
+ webComponentProps.tempo = tempo;
61
+ }
62
+
63
+ if (className) {
64
+ webComponentProps.class = className; // HTML attribute is 'class', not 'className'
65
+ }
66
+
67
+ return React.createElement('ty-icon', webComponentProps);
68
+ }
69
+ );
70
+
71
+ TyIcon.displayName = 'TyIcon';