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,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Example: React Event Convention
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates the new event handling convention
|
|
5
|
+
* for tyrell-react components.
|
|
6
|
+
*
|
|
7
|
+
* To test:
|
|
8
|
+
* 1. Create a new React project or use existing
|
|
9
|
+
* 2. npm install tyrell-react
|
|
10
|
+
* 3. Add this component to your app
|
|
11
|
+
* 4. Observe console logs and state updates
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React, { useState } from 'react';
|
|
15
|
+
import { TyInput } from './TyInput';
|
|
16
|
+
import { TyTextarea } from './TyTextarea';
|
|
17
|
+
import { TyCheckbox } from './TyCheckbox';
|
|
18
|
+
import type { TyInputEventDetail } from './TyInput';
|
|
19
|
+
import type { TyTextareaEventDetail } from './TyTextarea';
|
|
20
|
+
import type { TyCheckboxEventDetail } from './TyCheckbox';
|
|
21
|
+
|
|
22
|
+
export function EventConventionTest() {
|
|
23
|
+
const [inputValue, setInputValue] = useState('');
|
|
24
|
+
const [textareaValue, setTextareaValue] = useState('');
|
|
25
|
+
const [checked, setChecked] = useState(false);
|
|
26
|
+
const [logs, setLogs] = useState<string[]>([]);
|
|
27
|
+
|
|
28
|
+
const addLog = (message: string) => {
|
|
29
|
+
setLogs(prev => [...prev, `${new Date().toLocaleTimeString()}: ${message}`]);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="p-8 space-y-6 max-w-2xl">
|
|
34
|
+
<h1 className="text-2xl font-bold">React Event Convention Test</h1>
|
|
35
|
+
|
|
36
|
+
{/* Input Test */}
|
|
37
|
+
<div className="space-y-2">
|
|
38
|
+
<h2 className="text-lg font-semibold">TyInput Test</h2>
|
|
39
|
+
<TyInput
|
|
40
|
+
label="Email"
|
|
41
|
+
placeholder="Type to test onChange..."
|
|
42
|
+
value={inputValue}
|
|
43
|
+
onChange={(e: CustomEvent<TyInputEventDetail>) => {
|
|
44
|
+
setInputValue(e.detail.value);
|
|
45
|
+
addLog(`onChange: "${e.detail.value}" (fires on keystroke)`);
|
|
46
|
+
}}
|
|
47
|
+
onChangeCommit={(e: CustomEvent<TyInputEventDetail>) => {
|
|
48
|
+
addLog(`onChangeCommit: "${e.detail.value}" (fires on blur)`);
|
|
49
|
+
}}
|
|
50
|
+
onFocus={() => addLog('onFocus')}
|
|
51
|
+
onBlur={() => addLog('onBlur')}
|
|
52
|
+
/>
|
|
53
|
+
<p className="text-sm text-gray-600">
|
|
54
|
+
Current value: <strong>{inputValue}</strong>
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{/* Textarea Test */}
|
|
59
|
+
<div className="space-y-2">
|
|
60
|
+
<h2 className="text-lg font-semibold">TyTextarea Test</h2>
|
|
61
|
+
<TyTextarea
|
|
62
|
+
label="Comments"
|
|
63
|
+
placeholder="Type to test onChange..."
|
|
64
|
+
value={textareaValue}
|
|
65
|
+
rows={3}
|
|
66
|
+
onChange={(e: CustomEvent<TyTextareaEventDetail>) => {
|
|
67
|
+
setTextareaValue(e.detail.value);
|
|
68
|
+
addLog(`Textarea onChange: "${e.detail.value}"`);
|
|
69
|
+
}}
|
|
70
|
+
onChangeCommit={(e: CustomEvent<TyTextareaEventDetail>) => {
|
|
71
|
+
addLog(`Textarea onChangeCommit: "${e.detail.value}"`);
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
<p className="text-sm text-gray-600">
|
|
75
|
+
Current value: <strong>{textareaValue}</strong>
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Checkbox Test */}
|
|
80
|
+
<div className="space-y-2">
|
|
81
|
+
<h2 className="text-lg font-semibold">TyCheckbox Test</h2>
|
|
82
|
+
<TyCheckbox
|
|
83
|
+
checked={checked}
|
|
84
|
+
onChange={(e: CustomEvent<TyCheckboxEventDetail>) => {
|
|
85
|
+
setChecked(e.detail.checked);
|
|
86
|
+
addLog(`Checkbox onChange: ${e.detail.checked} (fires immediately)`);
|
|
87
|
+
}}
|
|
88
|
+
onChangeCommit={(e: CustomEvent<TyCheckboxEventDetail>) => {
|
|
89
|
+
addLog(`Checkbox onChangeCommit: ${e.detail.checked} (fires on blur)`);
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
Subscribe to newsletter
|
|
93
|
+
</TyCheckbox>
|
|
94
|
+
<p className="text-sm text-gray-600">
|
|
95
|
+
Current state: <strong>{checked ? 'Checked' : 'Unchecked'}</strong>
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{/* Event Log */}
|
|
100
|
+
<div className="space-y-2">
|
|
101
|
+
<h2 className="text-lg font-semibold">Event Log</h2>
|
|
102
|
+
<div className="bg-gray-100 p-4 rounded max-h-64 overflow-y-auto font-mono text-xs">
|
|
103
|
+
{logs.length === 0 ? (
|
|
104
|
+
<p className="text-gray-500">No events yet. Start typing or checking boxes!</p>
|
|
105
|
+
) : (
|
|
106
|
+
logs.map((log, i) => (
|
|
107
|
+
<div key={i} className="text-gray-800">
|
|
108
|
+
{log}
|
|
109
|
+
</div>
|
|
110
|
+
))
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
<button
|
|
114
|
+
onClick={() => setLogs([])}
|
|
115
|
+
className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
|
|
116
|
+
>
|
|
117
|
+
Clear Log
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{/* Expected Behavior */}
|
|
122
|
+
<div className="bg-blue-50 p-4 rounded">
|
|
123
|
+
<h3 className="font-semibold mb-2">Expected Behavior:</h3>
|
|
124
|
+
<ul className="list-disc list-inside space-y-1 text-sm">
|
|
125
|
+
<li>
|
|
126
|
+
<strong>onChange</strong> - Fires on every keystroke/state change (React convention)
|
|
127
|
+
</li>
|
|
128
|
+
<li>
|
|
129
|
+
<strong>onChangeCommit</strong> - Fires on blur if value changed (optional)
|
|
130
|
+
</li>
|
|
131
|
+
<li>
|
|
132
|
+
<strong>onFocus</strong> - Fires when element gains focus
|
|
133
|
+
</li>
|
|
134
|
+
<li>
|
|
135
|
+
<strong>onBlur</strong> - Fires when element loses focus
|
|
136
|
+
</li>
|
|
137
|
+
</ul>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{/* Verification */}
|
|
141
|
+
<div className="bg-green-50 p-4 rounded">
|
|
142
|
+
<h3 className="font-semibold mb-2">Verification Checklist:</h3>
|
|
143
|
+
<ul className="list-disc list-inside space-y-1 text-sm">
|
|
144
|
+
<li>✅ onChange fires on EVERY keystroke (not just on blur)</li>
|
|
145
|
+
<li>✅ State updates in real-time as you type</li>
|
|
146
|
+
<li>✅ onChangeCommit fires ONLY on blur (if value changed)</li>
|
|
147
|
+
<li>✅ Event order: onChange → onBlur → onChangeCommit</li>
|
|
148
|
+
<li>✅ Checkbox onChange fires immediately on click</li>
|
|
149
|
+
</ul>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default EventConventionTest;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
type BuiltinFlavor = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
|
|
4
|
+
type ShadedFlavor = BuiltinFlavor | `${BuiltinFlavor}+` | `${BuiltinFlavor}-`;
|
|
5
|
+
type ButtonAppearance = 'solid' | 'outlined' | 'ghost';
|
|
6
|
+
|
|
7
|
+
export interface TyButtonCSSProperties extends React.CSSProperties {
|
|
8
|
+
'--ty-button-bg'?: string;
|
|
9
|
+
'--ty-button-bg-hover'?: string;
|
|
10
|
+
'--ty-button-color'?: string;
|
|
11
|
+
'--ty-button-border'?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TyButtonProps extends Omit<React.HTMLAttributes<HTMLElement>, 'style'> {
|
|
15
|
+
style?: TyButtonCSSProperties;
|
|
16
|
+
/**
|
|
17
|
+
* Semantic styling variant. Built-in flavors get themed styles; append `+`
|
|
18
|
+
* for a stronger shade or `-` for a softer one (e.g. `"primary+"`,
|
|
19
|
+
* `"danger-"`). Any other string is passed through as-is — theme it via
|
|
20
|
+
* `--ty-button-*` CSS variables.
|
|
21
|
+
*/
|
|
22
|
+
flavor?: ShadedFlavor | (string & {});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Visual appearance:
|
|
26
|
+
* - `"solid"` (default) — saturated brand fill with paired text color
|
|
27
|
+
* - `"outlined"` — transparent background, text === border
|
|
28
|
+
* - `"ghost"` — text only with hover background
|
|
29
|
+
*/
|
|
30
|
+
appearance?: ButtonAppearance;
|
|
31
|
+
|
|
32
|
+
/** Button size */
|
|
33
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
34
|
+
|
|
35
|
+
/** Button type for form submission */
|
|
36
|
+
type?: 'button' | 'submit' | 'reset';
|
|
37
|
+
|
|
38
|
+
/** Disable the button */
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
|
|
41
|
+
/** Pill-shaped button (rounded ends) */
|
|
42
|
+
pill?: boolean;
|
|
43
|
+
|
|
44
|
+
/** Action (icon-only square) */
|
|
45
|
+
action?: boolean;
|
|
46
|
+
|
|
47
|
+
/** Accessible label for screen readers */
|
|
48
|
+
label?: string;
|
|
49
|
+
|
|
50
|
+
/** Form field name for form submission */
|
|
51
|
+
name?: string;
|
|
52
|
+
|
|
53
|
+
/** Form field value for form submission */
|
|
54
|
+
value?: string;
|
|
55
|
+
|
|
56
|
+
/** Full-width button */
|
|
57
|
+
wide?: boolean;
|
|
58
|
+
|
|
59
|
+
/** Button content */
|
|
60
|
+
children?: React.ReactNode;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const TyButton = React.forwardRef<HTMLElement, TyButtonProps>(
|
|
64
|
+
({
|
|
65
|
+
children,
|
|
66
|
+
type,
|
|
67
|
+
appearance,
|
|
68
|
+
disabled,
|
|
69
|
+
pill,
|
|
70
|
+
action,
|
|
71
|
+
wide,
|
|
72
|
+
label,
|
|
73
|
+
name,
|
|
74
|
+
value,
|
|
75
|
+
...props
|
|
76
|
+
}, ref) => {
|
|
77
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
78
|
+
|
|
79
|
+
const handleFormSubmission = useCallback((event: Event) => {
|
|
80
|
+
const element = elementRef.current;
|
|
81
|
+
if (!element || type !== 'submit') return;
|
|
82
|
+
|
|
83
|
+
const form = element.closest('form');
|
|
84
|
+
if (!form) return;
|
|
85
|
+
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
event.stopPropagation();
|
|
88
|
+
|
|
89
|
+
const syntheticEvent = new Event('submit', {
|
|
90
|
+
bubbles: true,
|
|
91
|
+
cancelable: true
|
|
92
|
+
});
|
|
93
|
+
form.dispatchEvent(syntheticEvent);
|
|
94
|
+
}, [type]);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const element = elementRef.current;
|
|
98
|
+
if (!element || type !== 'submit') return;
|
|
99
|
+
|
|
100
|
+
element.addEventListener('click', handleFormSubmission);
|
|
101
|
+
return () => {
|
|
102
|
+
element.removeEventListener('click', handleFormSubmission);
|
|
103
|
+
};
|
|
104
|
+
}, [type, handleFormSubmission]);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (ref && elementRef.current) {
|
|
108
|
+
if (typeof ref === 'function') {
|
|
109
|
+
ref(elementRef.current);
|
|
110
|
+
} else {
|
|
111
|
+
ref.current = elementRef.current;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}, [ref]);
|
|
115
|
+
|
|
116
|
+
const webComponentProps: Record<string, any> = {
|
|
117
|
+
...props,
|
|
118
|
+
ref: elementRef,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (disabled) webComponentProps.disabled = '';
|
|
122
|
+
if (pill) webComponentProps.pill = '';
|
|
123
|
+
if (action) webComponentProps.action = '';
|
|
124
|
+
if (wide) webComponentProps.wide = '';
|
|
125
|
+
|
|
126
|
+
if (appearance) webComponentProps.appearance = appearance;
|
|
127
|
+
if (type) webComponentProps.type = type;
|
|
128
|
+
if (label) webComponentProps.label = label;
|
|
129
|
+
if (name) webComponentProps.name = name;
|
|
130
|
+
if (value) webComponentProps.value = value;
|
|
131
|
+
|
|
132
|
+
return React.createElement(
|
|
133
|
+
'ty-button',
|
|
134
|
+
webComponentProps,
|
|
135
|
+
children
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
TyButton.displayName = 'TyButton';
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
// Type definitions for Ty Calendar component
|
|
4
|
+
export interface TyCalendarChangeEventDetail {
|
|
5
|
+
/** Selected month (1-12) */
|
|
6
|
+
month: number;
|
|
7
|
+
/** Selected year (4-digit) */
|
|
8
|
+
year: number;
|
|
9
|
+
/** Selected day (1-31) */
|
|
10
|
+
day: number;
|
|
11
|
+
/** Action that triggered the change: "select" */
|
|
12
|
+
action: 'select';
|
|
13
|
+
/** Source of the change: "day-click" */
|
|
14
|
+
source: 'day-click';
|
|
15
|
+
/** Complete day context from the calendar month */
|
|
16
|
+
dayContext: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TyCalendarNavigateEventDetail {
|
|
20
|
+
/** Navigation target month (1-12) */
|
|
21
|
+
month: number;
|
|
22
|
+
/** Navigation target year (4-digit) */
|
|
23
|
+
year: number;
|
|
24
|
+
/** Action that triggered the navigation: "navigate" */
|
|
25
|
+
action: 'navigate';
|
|
26
|
+
/** Source of the change: "navigation" */
|
|
27
|
+
source: 'navigation';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TyCalendarProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
31
|
+
/** Selected year (4-digit) */
|
|
32
|
+
year?: number | string;
|
|
33
|
+
|
|
34
|
+
/** Selected month (1-12) */
|
|
35
|
+
month?: number | string;
|
|
36
|
+
|
|
37
|
+
/** Selected day (1-31) */
|
|
38
|
+
day?: number | string;
|
|
39
|
+
|
|
40
|
+
/** Show navigation controls */
|
|
41
|
+
showNavigation?: boolean;
|
|
42
|
+
|
|
43
|
+
/** Stateless mode - no internal state management */
|
|
44
|
+
stateless?: boolean;
|
|
45
|
+
|
|
46
|
+
/** Calendar size */
|
|
47
|
+
size?: 'sm' | 'md' | 'lg';
|
|
48
|
+
|
|
49
|
+
/** Locale for date formatting */
|
|
50
|
+
locale?: string;
|
|
51
|
+
|
|
52
|
+
/** Calendar width */
|
|
53
|
+
width?: string | number;
|
|
54
|
+
|
|
55
|
+
/** Minimum calendar width */
|
|
56
|
+
minWidth?: string | number;
|
|
57
|
+
|
|
58
|
+
/** Maximum calendar width */
|
|
59
|
+
maxWidth?: string | number;
|
|
60
|
+
|
|
61
|
+
/** Form field name for form submission */
|
|
62
|
+
name?: string;
|
|
63
|
+
|
|
64
|
+
/** Form value (ISO date string) */
|
|
65
|
+
value?: string;
|
|
66
|
+
|
|
67
|
+
/** Function to render custom day content */
|
|
68
|
+
dayContentFn?: (dayContext: any) => HTMLElement | string;
|
|
69
|
+
|
|
70
|
+
/** Function to determine day CSS classes */
|
|
71
|
+
dayClassesFn?: (dayContext: any) => string[];
|
|
72
|
+
|
|
73
|
+
/** Custom CSS injection for render functions */
|
|
74
|
+
customCSS?: string;
|
|
75
|
+
|
|
76
|
+
/** Callback when a date is selected */
|
|
77
|
+
onChange?: (event: CustomEvent<TyCalendarChangeEventDetail>) => void;
|
|
78
|
+
|
|
79
|
+
/** Callback when navigation changes month/year */
|
|
80
|
+
onNavigate?: (event: CustomEvent<TyCalendarNavigateEventDetail>) => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// React wrapper for ty-calendar web component
|
|
84
|
+
export const TyCalendar = React.forwardRef<HTMLElement, TyCalendarProps>(
|
|
85
|
+
({
|
|
86
|
+
year,
|
|
87
|
+
month,
|
|
88
|
+
day,
|
|
89
|
+
showNavigation,
|
|
90
|
+
stateless,
|
|
91
|
+
size,
|
|
92
|
+
locale,
|
|
93
|
+
width,
|
|
94
|
+
minWidth,
|
|
95
|
+
maxWidth,
|
|
96
|
+
name,
|
|
97
|
+
value,
|
|
98
|
+
dayContentFn,
|
|
99
|
+
dayClassesFn,
|
|
100
|
+
customCSS,
|
|
101
|
+
onChange,
|
|
102
|
+
onNavigate,
|
|
103
|
+
...props
|
|
104
|
+
}, ref) => {
|
|
105
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
106
|
+
|
|
107
|
+
// Handle ref forwarding
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (ref && elementRef.current) {
|
|
110
|
+
if (typeof ref === 'function') {
|
|
111
|
+
ref(elementRef.current);
|
|
112
|
+
} else {
|
|
113
|
+
ref.current = elementRef.current;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}, [ref]);
|
|
117
|
+
|
|
118
|
+
// Handle change events (date selection)
|
|
119
|
+
const handleChange = useCallback((event: Event) => {
|
|
120
|
+
const customEvent = event as CustomEvent<TyCalendarChangeEventDetail>;
|
|
121
|
+
if (onChange) {
|
|
122
|
+
onChange(customEvent);
|
|
123
|
+
}
|
|
124
|
+
}, [onChange]);
|
|
125
|
+
|
|
126
|
+
// Handle navigate events (month/year navigation)
|
|
127
|
+
const handleNavigate = useCallback((event: Event) => {
|
|
128
|
+
const customEvent = event as CustomEvent<TyCalendarNavigateEventDetail>;
|
|
129
|
+
if (onNavigate) {
|
|
130
|
+
onNavigate(customEvent);
|
|
131
|
+
}
|
|
132
|
+
}, [onNavigate]);
|
|
133
|
+
|
|
134
|
+
// Set up event listeners
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
const element = elementRef.current;
|
|
137
|
+
if (!element) return;
|
|
138
|
+
|
|
139
|
+
const listeners: Array<[string, EventListener]> = [];
|
|
140
|
+
|
|
141
|
+
if (onChange) {
|
|
142
|
+
element.addEventListener('change', handleChange);
|
|
143
|
+
listeners.push(['change', handleChange]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (onNavigate) {
|
|
147
|
+
element.addEventListener('navigate', handleNavigate);
|
|
148
|
+
listeners.push(['navigate', handleNavigate]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return () => {
|
|
152
|
+
listeners.forEach(([eventName, handler]) => {
|
|
153
|
+
element.removeEventListener(eventName, handler);
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
}, [handleChange, handleNavigate, onChange, onNavigate]);
|
|
157
|
+
|
|
158
|
+
// Set function properties directly on the element (preferred over attributes)
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const element = elementRef.current;
|
|
161
|
+
if (!element) return;
|
|
162
|
+
|
|
163
|
+
// Day content function property (preferred over attribute)
|
|
164
|
+
if (dayContentFn) {
|
|
165
|
+
(element as any).dayContentFn = dayContentFn;
|
|
166
|
+
} else {
|
|
167
|
+
(element as any).dayContentFn = null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Day classes function property (preferred over attribute)
|
|
171
|
+
if (dayClassesFn) {
|
|
172
|
+
(element as any).dayClassesFn = dayClassesFn;
|
|
173
|
+
} else {
|
|
174
|
+
(element as any).dayClassesFn = null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Custom CSS property
|
|
178
|
+
if (customCSS) {
|
|
179
|
+
(element as any).customCSS = customCSS;
|
|
180
|
+
} else {
|
|
181
|
+
(element as any).customCSS = null;
|
|
182
|
+
}
|
|
183
|
+
}, [dayContentFn, dayClassesFn, customCSS]);
|
|
184
|
+
|
|
185
|
+
// Convert React props to web component attributes
|
|
186
|
+
const webComponentProps: Record<string, any> = {
|
|
187
|
+
...props,
|
|
188
|
+
ref: elementRef,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Add optional attributes only if they have values
|
|
192
|
+
if (year !== undefined) {
|
|
193
|
+
webComponentProps.year = year.toString();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (month !== undefined) {
|
|
197
|
+
webComponentProps.month = month.toString();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (day !== undefined) {
|
|
201
|
+
webComponentProps.day = day.toString();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (showNavigation) {
|
|
205
|
+
webComponentProps['show-navigation'] = ''; // Boolean attributes as empty string
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (stateless) {
|
|
209
|
+
webComponentProps.stateless = ''; // Boolean attributes as empty string
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (size) {
|
|
213
|
+
webComponentProps.size = size;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (locale) {
|
|
217
|
+
webComponentProps.locale = locale;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (width !== undefined) {
|
|
221
|
+
webComponentProps.width = width.toString();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (minWidth !== undefined) {
|
|
225
|
+
webComponentProps['min-width'] = minWidth.toString();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (maxWidth !== undefined) {
|
|
229
|
+
webComponentProps['max-width'] = maxWidth.toString();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (name) {
|
|
233
|
+
webComponentProps.name = name;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (value) {
|
|
237
|
+
webComponentProps.value = value;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return React.createElement('ty-calendar', webComponentProps);
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
TyCalendar.displayName = 'TyCalendar';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
// Type definitions for Ty Calendar Month component
|
|
4
|
+
export interface TyCalendarMonthProps extends React.HTMLAttributes<HTMLElement> {
|
|
5
|
+
/** Display year */
|
|
6
|
+
displayYear?: number;
|
|
7
|
+
|
|
8
|
+
/** Display month (1-12) */
|
|
9
|
+
displayMonth?: number;
|
|
10
|
+
|
|
11
|
+
/** Locale for date formatting */
|
|
12
|
+
locale?: string;
|
|
13
|
+
|
|
14
|
+
/** Calendar size */
|
|
15
|
+
size?: 'sm' | 'md' | 'lg';
|
|
16
|
+
|
|
17
|
+
/** Width of calendar */
|
|
18
|
+
width?: string;
|
|
19
|
+
|
|
20
|
+
/** Minimum width */
|
|
21
|
+
minWidth?: string;
|
|
22
|
+
|
|
23
|
+
/** Maximum width */
|
|
24
|
+
maxWidth?: string;
|
|
25
|
+
|
|
26
|
+
/** Day click event handler */
|
|
27
|
+
onDayClick?: (event: CustomEvent<DayClickDetail>) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DayClickDetail {
|
|
31
|
+
dayContext: any; // DayContext type from calendar-utils
|
|
32
|
+
value: number;
|
|
33
|
+
year: number;
|
|
34
|
+
month: number;
|
|
35
|
+
day: number;
|
|
36
|
+
isHoliday?: boolean;
|
|
37
|
+
isToday?: boolean;
|
|
38
|
+
isWeekend: boolean;
|
|
39
|
+
isOtherMonth: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// React wrapper for ty-calendar-month web component
|
|
43
|
+
export const TyCalendarMonth = React.forwardRef<HTMLElement, TyCalendarMonthProps>(
|
|
44
|
+
({
|
|
45
|
+
displayYear,
|
|
46
|
+
displayMonth,
|
|
47
|
+
locale,
|
|
48
|
+
size,
|
|
49
|
+
width,
|
|
50
|
+
minWidth,
|
|
51
|
+
maxWidth,
|
|
52
|
+
onDayClick,
|
|
53
|
+
...props
|
|
54
|
+
}, ref) => {
|
|
55
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
56
|
+
|
|
57
|
+
// Handle day click events
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const element = elementRef.current;
|
|
60
|
+
if (!element) return;
|
|
61
|
+
|
|
62
|
+
const handleDayClick = (event: Event) => {
|
|
63
|
+
if (onDayClick) {
|
|
64
|
+
onDayClick(event as CustomEvent<DayClickDetail>);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
element.addEventListener('day-click', handleDayClick);
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
element.removeEventListener('day-click', handleDayClick);
|
|
72
|
+
};
|
|
73
|
+
}, [onDayClick]);
|
|
74
|
+
|
|
75
|
+
// Combine refs if needed
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (ref && elementRef.current) {
|
|
78
|
+
if (typeof ref === 'function') {
|
|
79
|
+
ref(elementRef.current);
|
|
80
|
+
} else {
|
|
81
|
+
ref.current = elementRef.current;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}, [ref]);
|
|
85
|
+
|
|
86
|
+
// Convert React props to web component attributes
|
|
87
|
+
const webComponentProps: Record<string, any> = {
|
|
88
|
+
...props,
|
|
89
|
+
ref: elementRef,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Add attributes
|
|
93
|
+
if (displayYear !== undefined) webComponentProps['display-year'] = displayYear;
|
|
94
|
+
if (displayMonth !== undefined) webComponentProps['display-month'] = displayMonth;
|
|
95
|
+
if (locale) webComponentProps.locale = locale;
|
|
96
|
+
if (size) webComponentProps.size = size;
|
|
97
|
+
if (width) webComponentProps.width = width;
|
|
98
|
+
if (minWidth) webComponentProps['min-width'] = minWidth;
|
|
99
|
+
if (maxWidth) webComponentProps['max-width'] = maxWidth;
|
|
100
|
+
|
|
101
|
+
return React.createElement(
|
|
102
|
+
'ty-calendar-month',
|
|
103
|
+
webComponentProps
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
TyCalendarMonth.displayName = 'TyCalendarMonth';
|