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,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';
@@ -0,0 +1,200 @@
1
+ import React, { useEffect, useRef, useCallback } from 'react';
2
+
3
+ // Type definitions for Ty Multiselect component
4
+ export interface TyMultiselectEventDetail {
5
+ /** Array of currently selected values */
6
+ values: string[];
7
+ /** Action that triggered the change: "add" | "remove" */
8
+ action: 'add' | 'remove';
9
+ /** The specific item that was added or removed */
10
+ item: string;
11
+ }
12
+
13
+ export interface TyMultiselectProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'style'> {
14
+ style?: import('./TyInput').TyInputCSSProperties;
15
+ /** Current selected values as comma-separated string or array */
16
+ value?: string | string[];
17
+
18
+ /** Placeholder text when no items are selected */
19
+ placeholder?: string;
20
+
21
+ /** Disable the multiselect component */
22
+ disabled?: boolean;
23
+
24
+ /** Make the multiselect read-only */
25
+ readonly?: boolean;
26
+
27
+ /** Semantic styling variant */
28
+ flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
29
+
30
+ /** Label text for the multiselect */
31
+ label?: string;
32
+
33
+ /** Mark the field as required */
34
+ required?: boolean;
35
+
36
+ /**
37
+ * Switch to external (remote) search mode. Default is `false` — the
38
+ * multiselect filters its own children client-side. Set to `true` when you
39
+ * want to handle filtering yourself (e.g. server-side): the component will
40
+ * dispatch `search` events on each keystroke, and you must update the children
41
+ * in response. The `onSearch` prop receives those events.
42
+ */
43
+ externalSearch?: boolean;
44
+
45
+ /** Debounce in milliseconds (0-5000) */
46
+ debounce?: number;
47
+
48
+ /** Mobile section label for selected items */
49
+ selectedLabel?: string;
50
+
51
+ /** Form field name for form submission */
52
+ name?: string;
53
+
54
+ /** Callback when selection changes */
55
+ onChange?: (event: CustomEvent<TyMultiselectEventDetail>) => void;
56
+
57
+ /** Callback fired on each search input change (debounced by `debounce`). Use for external/server-side filtering. */
58
+ onSearch?: (event: CustomEvent<{ query: string; element: HTMLElement }>) => void;
59
+
60
+ /** Children should be TyTag components, not TyOption */
61
+ children?: React.ReactNode;
62
+ }
63
+
64
+ // React wrapper for ty-multiselect web component
65
+ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
66
+ ({
67
+ value,
68
+ placeholder,
69
+ disabled,
70
+ readonly,
71
+ flavor,
72
+ label,
73
+ required,
74
+ externalSearch,
75
+ debounce,
76
+ selectedLabel,
77
+ name,
78
+ onChange,
79
+ onSearch,
80
+ children,
81
+ ...props
82
+ }, ref) => {
83
+ const elementRef = useRef<HTMLElement>(null);
84
+
85
+ // Handle ref forwarding
86
+ useEffect(() => {
87
+ if (ref && elementRef.current) {
88
+ if (typeof ref === 'function') {
89
+ ref(elementRef.current);
90
+ } else {
91
+ ref.current = elementRef.current;
92
+ }
93
+ }
94
+ }, [ref]);
95
+
96
+ // Imperatively sync `value` to the underlying property so resets
97
+ // (`value=""` or null) reliably clear the visible selection.
98
+ useEffect(() => {
99
+ const element = elementRef.current as any;
100
+ if (!element) return;
101
+ const next = (props as any).value ?? '';
102
+ if (element.value !== next) {
103
+ element.value = next;
104
+ }
105
+ }, [(props as any).value]);
106
+
107
+ // Handle change events
108
+ const handleChange = useCallback((event: Event) => {
109
+ const customEvent = event as CustomEvent<TyMultiselectEventDetail>;
110
+ if (onChange) {
111
+ onChange(customEvent);
112
+ }
113
+ }, [onChange]);
114
+
115
+ const handleSearch = useCallback((event: Event) => {
116
+ const customEvent = event as CustomEvent<{ query: string; element: HTMLElement }>;
117
+ if (onSearch) {
118
+ onSearch(customEvent);
119
+ }
120
+ }, [onSearch]);
121
+
122
+ // Set up event listeners
123
+ useEffect(() => {
124
+ const element = elementRef.current;
125
+ if (!element) return;
126
+
127
+ if (onChange) element.addEventListener('change', handleChange);
128
+ if (onSearch) element.addEventListener('search', handleSearch);
129
+
130
+ return () => {
131
+ if (onChange) element.removeEventListener('change', handleChange);
132
+ if (onSearch) element.removeEventListener('search', handleSearch);
133
+ };
134
+ }, [handleChange, handleSearch, onChange, onSearch]);
135
+
136
+ // Convert React props to web component attributes
137
+ const webComponentProps: Record<string, any> = {
138
+ ...props,
139
+ ref: elementRef,
140
+ };
141
+
142
+ // Handle value conversion (array to comma-separated string)
143
+ if (value !== undefined) {
144
+ const valueString = Array.isArray(value) ? value.join(',') : value;
145
+ webComponentProps.value = valueString;
146
+ }
147
+
148
+ // Add optional attributes only if they have values
149
+ if (placeholder) {
150
+ webComponentProps.placeholder = placeholder;
151
+ }
152
+
153
+ if (disabled) {
154
+ webComponentProps.disabled = ''; // Boolean attributes as empty string
155
+ }
156
+
157
+ if (readonly) {
158
+ webComponentProps.readonly = ''; // Boolean attributes as empty string
159
+ }
160
+
161
+ if (flavor) {
162
+ webComponentProps.flavor = flavor;
163
+ }
164
+
165
+ if (label) {
166
+ webComponentProps.label = label;
167
+ }
168
+
169
+ if (required) {
170
+ webComponentProps.required = ''; // Boolean attributes as empty string
171
+ }
172
+
173
+ if (name) {
174
+ webComponentProps.name = name;
175
+ }
176
+
177
+ // External (remote) search mode: parent owns filtering, multiselect dispatches search events
178
+ if (externalSearch) {
179
+ webComponentProps['external-search'] = '';
180
+ }
181
+
182
+ // Add debounce attribute
183
+ if (debounce !== undefined) {
184
+ webComponentProps.debounce = debounce;
185
+ }
186
+
187
+ // Add selectedLabel attribute
188
+ if (selectedLabel) {
189
+ webComponentProps['selected-label'] = selectedLabel;
190
+ }
191
+
192
+ return React.createElement(
193
+ 'ty-multiselect',
194
+ webComponentProps,
195
+ children
196
+ );
197
+ }
198
+ );
199
+
200
+ TyMultiselect.displayName = 'TyMultiselect';
@@ -0,0 +1,42 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ // Type definitions for Ty Option component
4
+ export interface TyOptionProps extends React.HTMLAttributes<HTMLElement> {
5
+ value?: string;
6
+ disabled?: boolean;
7
+ selected?: boolean;
8
+ hidden?: boolean;
9
+ children?: React.ReactNode;
10
+ }
11
+
12
+ // React wrapper for ty-option web component
13
+ export const TyOption = React.forwardRef<HTMLElement, TyOptionProps>(
14
+ ({ children, disabled, selected, hidden, ...props }, ref) => {
15
+ const elementRef = useRef<HTMLElement>(null);
16
+
17
+ // Handle ref forwarding
18
+ useEffect(() => {
19
+ if (ref && elementRef.current) {
20
+ if (typeof ref === 'function') {
21
+ ref(elementRef.current);
22
+ } else {
23
+ ref.current = elementRef.current;
24
+ }
25
+ }
26
+ }, [ref]);
27
+
28
+ return React.createElement(
29
+ 'ty-option',
30
+ {
31
+ ...props,
32
+ ...(disabled && { disabled: "" }),
33
+ ...(selected && { selected: "" }),
34
+ ...(hidden && { hidden: "" }),
35
+ ref: elementRef,
36
+ },
37
+ children
38
+ );
39
+ }
40
+ );
41
+
42
+ TyOption.displayName = 'TyOption';
@@ -0,0 +1,111 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ // Type definitions for Ty Popup component
4
+ export interface TyPopupProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onClose'> {
5
+ /** Preferred placement of the popup relative to anchor parent: "top" | "bottom" | "left" | "right" */
6
+ placement?: 'top' | 'bottom' | 'left' | 'right';
7
+
8
+ /** Distance offset from the anchor in pixels (default: 8) */
9
+ offset?: number;
10
+
11
+ /** Disable automatic click trigger - requires manual open/close via ref methods */
12
+ manual?: boolean;
13
+
14
+ /** Disable automatic close on outside click and ESC key */
15
+ disableClose?: boolean;
16
+
17
+ /** Fired when the popup opens (after the open animation starts) */
18
+ onOpen?: (event: CustomEvent) => void;
19
+
20
+ /** Fired when the popup closes */
21
+ onClose?: (event: CustomEvent) => void;
22
+
23
+ /** Popup content - popup should be a child of the anchor element */
24
+ children?: React.ReactNode;
25
+ }
26
+
27
+ // Programmatic API for popup control
28
+ export interface TyPopupElement extends HTMLElement {
29
+ openPopup(): void;
30
+ closePopup(): void;
31
+ togglePopup(): void;
32
+ }
33
+
34
+ // React wrapper for ty-popup web component
35
+ export const TyPopup = React.forwardRef<TyPopupElement, TyPopupProps>(
36
+ ({
37
+ placement,
38
+ offset,
39
+ manual,
40
+ disableClose,
41
+ onOpen,
42
+ onClose,
43
+ children,
44
+ ...props
45
+ }, ref) => {
46
+ const elementRef = useRef<TyPopupElement>(null);
47
+
48
+ // Handle ref forwarding
49
+ useEffect(() => {
50
+ if (ref && elementRef.current) {
51
+ if (typeof ref === 'function') {
52
+ ref(elementRef.current);
53
+ } else {
54
+ ref.current = elementRef.current;
55
+ }
56
+ }
57
+ }, [ref]);
58
+
59
+ // Listen for popup open/close events
60
+ useEffect(() => {
61
+ const element = elementRef.current;
62
+ if (!element) return;
63
+
64
+ const handleOpen = (event: Event) => {
65
+ if (onOpen) onOpen(event as CustomEvent);
66
+ };
67
+ const handleClose = (event: Event) => {
68
+ if (onClose) onClose(event as CustomEvent);
69
+ };
70
+
71
+ if (onOpen) element.addEventListener('open', handleOpen);
72
+ if (onClose) element.addEventListener('close', handleClose);
73
+
74
+ return () => {
75
+ if (onOpen) element.removeEventListener('open', handleOpen);
76
+ if (onClose) element.removeEventListener('close', handleClose);
77
+ };
78
+ }, [onOpen, onClose]);
79
+
80
+ // Convert React props to web component attributes
81
+ const webComponentProps: Record<string, any> = {
82
+ ...props,
83
+ ref: elementRef,
84
+ };
85
+
86
+ // Add optional attributes only if they have values
87
+ if (placement) {
88
+ webComponentProps.placement = placement;
89
+ }
90
+
91
+ if (offset !== undefined) {
92
+ webComponentProps.offset = offset.toString();
93
+ }
94
+
95
+ if (manual) {
96
+ webComponentProps.manual = ''; // Boolean attributes as empty string
97
+ }
98
+
99
+ if (disableClose) {
100
+ webComponentProps['disable-close'] = ''; // Boolean attributes as empty string
101
+ }
102
+
103
+ return React.createElement(
104
+ 'ty-popup',
105
+ webComponentProps,
106
+ children
107
+ );
108
+ }
109
+ );
110
+
111
+ TyPopup.displayName = 'TyPopup';
@@ -0,0 +1,56 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ export interface TyRadioProps extends React.HTMLAttributes<HTMLElement> {
4
+ /** Form field value (selected by parent ty-radio-group when matches its `value`) */
5
+ value?: string;
6
+
7
+ /**
8
+ * Selected state. Usually managed by the parent `<TyRadioGroup>` based on its
9
+ * own `value`; set explicitly only when using `ty-radio` outside a group.
10
+ */
11
+ checked?: boolean;
12
+
13
+ /** Disable this individual radio */
14
+ disabled?: boolean;
15
+
16
+ /** Radio size — typically inherited from the parent group */
17
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
18
+
19
+ /** Semantic styling variant — typically inherited from the parent group */
20
+ flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
21
+
22
+ /** Label content (wrap in a `<label>` for click delegation, see ty-radio docs) */
23
+ children?: React.ReactNode;
24
+ }
25
+
26
+ export const TyRadio = React.forwardRef<HTMLElement, TyRadioProps>(
27
+ ({ children, value, checked, disabled, size, flavor, ...props }, ref) => {
28
+ const elementRef = useRef<HTMLElement>(null);
29
+
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
+ const webComponentProps: Record<string, any> = {
41
+ ...props,
42
+ ref: elementRef,
43
+ };
44
+
45
+ if (checked) webComponentProps.checked = '';
46
+ if (disabled) webComponentProps.disabled = '';
47
+
48
+ if (value !== undefined) webComponentProps.value = value;
49
+ if (size) webComponentProps.size = size;
50
+ if (flavor) webComponentProps.flavor = flavor;
51
+
52
+ return React.createElement('ty-radio', webComponentProps, children);
53
+ }
54
+ );
55
+
56
+ TyRadio.displayName = 'TyRadio';
@@ -0,0 +1,121 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ export interface TyRadioGroupProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'onInput'> {
4
+ /** Currently selected value (matches one child `<TyRadio value="...">`) */
5
+ value?: string;
6
+
7
+ /** Form field name */
8
+ name?: string;
9
+
10
+ /** Group label rendered above the radios */
11
+ label?: string;
12
+
13
+ /** Disable the entire group */
14
+ disabled?: boolean;
15
+
16
+ /** Required field — renders required-icon next to the label */
17
+ required?: boolean;
18
+
19
+ /** Error message rendered below the group */
20
+ error?: string;
21
+
22
+ /** Layout direction for radio children */
23
+ orientation?: 'vertical' | 'horizontal';
24
+
25
+ /** Group size — propagates to all `<TyRadio>` children */
26
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
27
+
28
+ /** Group flavor — propagates to all `<TyRadio>` children */
29
+ flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
30
+
31
+ /**
32
+ * Fires when selection changes (React convention)
33
+ * Maps to native 'input' event from ty-radio-group
34
+ */
35
+ onChange?: (event: CustomEvent<TyRadioGroupEventDetail>) => void;
36
+
37
+ /**
38
+ * Fires on blur if value changed (native DOM behavior)
39
+ * Maps to native 'change' event from ty-radio-group
40
+ */
41
+ onChangeCommit?: (event: CustomEvent<TyRadioGroupEventDetail>) => void;
42
+
43
+ /** `<TyRadio>` children */
44
+ children?: React.ReactNode;
45
+ }
46
+
47
+ export interface TyRadioGroupEventDetail {
48
+ value: string;
49
+ formValue: string;
50
+ originalEvent: Event;
51
+ }
52
+
53
+ export const TyRadioGroup = React.forwardRef<HTMLElement, TyRadioGroupProps>(
54
+ ({
55
+ children,
56
+ value,
57
+ name,
58
+ label,
59
+ disabled,
60
+ required,
61
+ error,
62
+ orientation,
63
+ size,
64
+ flavor,
65
+ onChange,
66
+ onChangeCommit,
67
+ ...props
68
+ }, ref) => {
69
+ const elementRef = useRef<HTMLElement>(null);
70
+
71
+ useEffect(() => {
72
+ const element = elementRef.current;
73
+ if (!element) return;
74
+
75
+ const handleInput = (event: Event) => {
76
+ if (onChange) onChange(event as CustomEvent<TyRadioGroupEventDetail>);
77
+ };
78
+ const handleChangeCommit = (event: Event) => {
79
+ if (onChangeCommit) onChangeCommit(event as CustomEvent<TyRadioGroupEventDetail>);
80
+ };
81
+
82
+ element.addEventListener('input', handleInput);
83
+ element.addEventListener('change', handleChangeCommit);
84
+
85
+ return () => {
86
+ element.removeEventListener('input', handleInput);
87
+ element.removeEventListener('change', handleChangeCommit);
88
+ };
89
+ }, [onChange, onChangeCommit]);
90
+
91
+ useEffect(() => {
92
+ if (ref && elementRef.current) {
93
+ if (typeof ref === 'function') {
94
+ ref(elementRef.current);
95
+ } else {
96
+ ref.current = elementRef.current;
97
+ }
98
+ }
99
+ }, [ref]);
100
+
101
+ const webComponentProps: Record<string, any> = {
102
+ ...props,
103
+ ref: elementRef,
104
+ };
105
+
106
+ if (disabled) webComponentProps.disabled = '';
107
+ if (required) webComponentProps.required = '';
108
+
109
+ if (value !== undefined) webComponentProps.value = value;
110
+ if (name) webComponentProps.name = name;
111
+ if (label) webComponentProps.label = label;
112
+ if (error) webComponentProps.error = error;
113
+ if (orientation) webComponentProps.orientation = orientation;
114
+ if (size) webComponentProps.size = size;
115
+ if (flavor) webComponentProps.flavor = flavor;
116
+
117
+ return React.createElement('ty-radio-group', webComponentProps, children);
118
+ }
119
+ );
120
+
121
+ TyRadioGroup.displayName = 'TyRadioGroup';