Skip to content

Commit ce91b0b

Browse files
authored
Merge branch 'main' into feat/core-radio-button-component
2 parents f90a965 + 0c24161 commit ce91b0b

File tree

9 files changed

+840
-42
lines changed

9 files changed

+840
-42
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React, { useState } from 'react';
4+
5+
import Textarea from '~components/textarea';
6+
7+
import ScreenshotArea from '../utils/screenshot-area';
8+
9+
export default function TextareaPseudoSelectorsPage() {
10+
const [value, setValue] = useState('Test value');
11+
const [isInvalid, setIsInvalid] = useState(false);
12+
const [isDisabled, setIsDisabled] = useState(false);
13+
const [isReadOnly, setIsReadOnly] = useState(false);
14+
const [isWarning, setIsWarning] = useState(false);
15+
const [customStyling, setCustomStyling] = useState(false);
16+
17+
const customStyle = {
18+
root: {
19+
borderColor: {
20+
default: '#3b82f6',
21+
hover: '#2563eb',
22+
focus: '#1d4ed8',
23+
disabled: '#93c5fd',
24+
readonly: '#60a5fa',
25+
},
26+
borderWidth: '2px',
27+
borderRadius: '8px',
28+
backgroundColor: {
29+
default: '#dbeafe',
30+
hover: '#bfdbfe',
31+
focus: '#bfdbfe',
32+
disabled: '#eff6ff',
33+
readonly: '#f0f9ff',
34+
},
35+
color: {
36+
default: '#1e40af',
37+
hover: '#1e40af',
38+
focus: '#1e3a8a',
39+
disabled: '#93c5fd',
40+
readonly: '#3b82f6',
41+
},
42+
fontSize: '16px',
43+
fontWeight: '500',
44+
paddingBlock: '10px',
45+
paddingInline: '14px',
46+
},
47+
placeholder: {
48+
color: '#60a5fa',
49+
fontSize: '14px',
50+
fontStyle: 'italic',
51+
},
52+
};
53+
54+
return (
55+
<>
56+
<h1>Textarea Style API - Pseudo Selectors</h1>
57+
<ScreenshotArea>
58+
<Textarea
59+
ariaLabel="Test textarea"
60+
value={value}
61+
onChange={event => setValue(event.detail.value)}
62+
invalid={isInvalid}
63+
disabled={isDisabled}
64+
readOnly={isReadOnly}
65+
warning={isWarning}
66+
placeholder="Enter text"
67+
data-testid="test-textarea"
68+
style={customStyling ? customStyle : undefined}
69+
/>
70+
</ScreenshotArea>
71+
72+
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', marginTop: '20px' }}>
73+
<button id="toggle-styling" onClick={() => setCustomStyling(!customStyling)} type="button">
74+
Toggle Custom Styling ({customStyling ? 'ON' : 'OFF'})
75+
</button>
76+
<button id="toggle-invalid" onClick={() => setIsInvalid(!isInvalid)} type="button">
77+
Toggle Invalid ({isInvalid ? 'ON' : 'OFF'})
78+
</button>
79+
<button id="toggle-disabled" onClick={() => setIsDisabled(!isDisabled)} type="button">
80+
Toggle Disabled ({isDisabled ? 'ON' : 'OFF'})
81+
</button>
82+
<button id="toggle-readonly" onClick={() => setIsReadOnly(!isReadOnly)} type="button">
83+
Toggle ReadOnly ({isReadOnly ? 'ON' : 'OFF'})
84+
</button>
85+
<button id="toggle-warning" onClick={() => setIsWarning(!isWarning)} type="button">
86+
Toggle Warning ({isWarning ? 'ON' : 'OFF'})
87+
</button>
88+
<button
89+
id="reset-all"
90+
onClick={() => {
91+
setCustomStyling(false);
92+
setIsInvalid(false);
93+
setIsDisabled(false);
94+
setIsReadOnly(false);
95+
setIsWarning(false);
96+
}}
97+
type="button"
98+
>
99+
Reset All
100+
</button>
101+
</div>
102+
</>
103+
);
104+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React from 'react';
4+
5+
import Textarea, { TextareaProps } from '~components/textarea';
6+
7+
import createPermutations from '../utils/permutations';
8+
import PermutationsView from '../utils/permutations-view';
9+
import ScreenshotArea from '../utils/screenshot-area';
10+
11+
const permutations = createPermutations<TextareaProps>([
12+
{
13+
value: ['This is an example value'],
14+
placeholder: [''],
15+
disabled: [false, true],
16+
readOnly: [false, true],
17+
onChange: [() => {}],
18+
style: [
19+
{
20+
root: {
21+
borderColor: {
22+
default: '#14b8a6',
23+
hover: '#0f766e',
24+
focus: '#0d9488',
25+
disabled: '#99f6e4',
26+
readonly: '#5eead4',
27+
},
28+
borderWidth: '2px',
29+
borderRadius: '8px',
30+
backgroundColor: {
31+
default: '#99f6e4',
32+
hover: '#5eead4',
33+
focus: '#5eead4',
34+
disabled: '#5eead4',
35+
readonly: '#ccfbf1',
36+
},
37+
boxShadow: {
38+
default: '0 1px 2px rgba(0, 0, 0, 0.05)',
39+
hover: '0 2px 4px rgba(20, 184, 166, 0.15)',
40+
focus: '0 0 0 4px rgba(20, 184, 166, 0.2), 0 2px 4px rgba(20, 184, 166, 0.15)',
41+
disabled: 'none',
42+
readonly: '0 1px 2px rgba(0, 0, 0, 0.05)',
43+
},
44+
color: {
45+
default: '#0d5c54',
46+
hover: '#0d5c54',
47+
focus: '#0d5c54',
48+
disabled: '#0d5c54',
49+
readonly: '#0f766e',
50+
},
51+
fontSize: '16px',
52+
fontWeight: '500',
53+
paddingBlock: '12px',
54+
paddingInline: '16px',
55+
},
56+
placeholder: {
57+
color: '#14b8a6',
58+
fontSize: '14px',
59+
fontStyle: 'italic',
60+
fontWeight: '400',
61+
},
62+
},
63+
{
64+
root: {
65+
borderColor: {
66+
default: '#ef4444',
67+
hover: '#b91c1c',
68+
focus: '#dc2626',
69+
disabled: '#fca5a5',
70+
readonly: '#fca5a5',
71+
},
72+
borderWidth: '2px',
73+
borderRadius: '8px',
74+
backgroundColor: {
75+
default: '#fecaca',
76+
hover: '#fca5a5',
77+
focus: '#fca5a5',
78+
disabled: '#fca5a5',
79+
readonly: '#fee2e2',
80+
},
81+
boxShadow: {
82+
default: '0 1px 2px rgba(0, 0, 0, 0.05)',
83+
hover: '0 2px 4px rgba(239, 68, 68, 0.15)',
84+
focus: '0 0 0 4px rgba(239, 68, 68, 0.2), 0 2px 4px rgba(239, 68, 68, 0.15)',
85+
disabled: 'none',
86+
readonly: '0 1px 2px rgba(0, 0, 0, 0.05)',
87+
},
88+
color: {
89+
default: '#7f1d1d',
90+
hover: '#7f1d1d',
91+
focus: '#7f1d1d',
92+
disabled: '#7f1d1d',
93+
readonly: '#991b1b',
94+
},
95+
},
96+
},
97+
{
98+
root: {
99+
borderColor: {
100+
default: '#f59e0b',
101+
hover: '#b45309',
102+
focus: '#d97706',
103+
disabled: '#fcd34d',
104+
readonly: '#fcd34d',
105+
},
106+
borderWidth: '2px',
107+
borderRadius: '16px',
108+
backgroundColor: {
109+
default: '#fde68a',
110+
hover: '#fcd34d',
111+
focus: '#fcd34d',
112+
disabled: '#fcd34d',
113+
readonly: '#fef3c7',
114+
},
115+
boxShadow: {
116+
default: '0 2px 8px rgba(245, 158, 11, 0.15)',
117+
hover: '0 6px 16px rgba(245, 158, 11, 0.25)',
118+
focus: '0 0 0 4px rgba(245, 158, 11, 0.25), 0 6px 16px rgba(245, 158, 11, 0.3)',
119+
disabled: 'none',
120+
readonly: '0 2px 8px rgba(245, 158, 11, 0.15)',
121+
},
122+
color: {
123+
default: '#78350f',
124+
hover: '#78350f',
125+
focus: '#78350f',
126+
disabled: '#78350f',
127+
readonly: '#92400e',
128+
},
129+
},
130+
},
131+
{
132+
root: {
133+
borderColor: {
134+
default: '#10b981',
135+
hover: '#047857',
136+
focus: '#059669',
137+
disabled: '#6ee7b7',
138+
readonly: '#6ee7b7',
139+
},
140+
borderWidth: '3px',
141+
borderRadius: '0px',
142+
backgroundColor: {
143+
default: '#d1fae5',
144+
hover: '#a7f3d0',
145+
focus: '#a7f3d0',
146+
disabled: '#a7f3d0',
147+
readonly: '#ecfdf5',
148+
},
149+
boxShadow: {
150+
default: '0 1px 2px rgba(0, 0, 0, 0.05)',
151+
hover: '0 2px 4px rgba(16, 185, 129, 0.1)',
152+
focus: '0 0 0 4px rgba(16, 185, 129, 0.2), 0 2px 4px rgba(16, 185, 129, 0.15)',
153+
disabled: 'none',
154+
readonly: '0 1px 2px rgba(0, 0, 0, 0.05)',
155+
},
156+
color: {
157+
default: '#064e3b',
158+
hover: '#064e3b',
159+
focus: '#064e3b',
160+
disabled: '#064e3b',
161+
readonly: '#065f46',
162+
},
163+
},
164+
},
165+
],
166+
},
167+
]);
168+
169+
export default function TextareaStylePermutations() {
170+
return (
171+
<>
172+
<h1>Textarea Style permutations</h1>
173+
<ScreenshotArea disableAnimations={true}>
174+
<PermutationsView
175+
permutations={permutations}
176+
render={permutation => <Textarea ariaLabel="Textarea field" {...permutation} />}
177+
/>
178+
</ScreenshotArea>
179+
</>
180+
);
181+
}

pages/utils/iframe-wrapper.tsx

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
import React, { useEffect, useRef } from 'react';
3+
import React, { useCallback, useRef } from 'react';
44

55
import { mount, unmount } from '~mount';
66

@@ -30,39 +30,44 @@ function syncClasses(from: HTMLElement, to: HTMLElement) {
3030
}
3131

3232
export function IframeWrapper({ id, AppComponent }: { id: string; AppComponent: React.ComponentType }) {
33-
const ref = useRef<HTMLDivElement>(null);
33+
const cleanupRef = useRef<(() => void) | null>(null);
3434

35-
useEffect(() => {
36-
const container = ref.current;
37-
if (!container) {
38-
return;
39-
}
40-
const iframeEl = container.ownerDocument.createElement('iframe');
41-
iframeEl.className = styles['full-screen'];
42-
iframeEl.id = id;
43-
iframeEl.title = id;
44-
container.appendChild(iframeEl);
35+
// use callback ref instead of useEffect to avoid double effect issues in React 18+ strict mode
36+
const mountIframe = useCallback(
37+
(container: HTMLElement | null) => {
38+
if (!container) {
39+
cleanupRef.current?.();
40+
cleanupRef.current = null;
41+
return;
42+
}
43+
const iframeEl = container.ownerDocument.createElement('iframe');
44+
iframeEl.className = styles['full-screen'];
45+
iframeEl.id = id;
46+
iframeEl.title = id;
47+
container.appendChild(iframeEl);
4548

46-
const iframeDocument = iframeEl.contentDocument!;
47-
// Prevent iframe document instance from reload
48-
// https://bugzilla.mozilla.org/show_bug.cgi?id=543435
49-
iframeDocument.open();
50-
// set html5 doctype
51-
iframeDocument.writeln('<!DOCTYPE html>');
52-
iframeDocument.close();
49+
const iframeDocument = iframeEl.contentDocument!;
50+
// Prevent iframe document instance from reload
51+
// https://bugzilla.mozilla.org/show_bug.cgi?id=543435
52+
iframeDocument.open();
53+
// set html5 doctype
54+
iframeDocument.writeln('<!DOCTYPE html>');
55+
iframeDocument.close();
5356

54-
const innerAppRoot = iframeDocument.createElement('div');
55-
iframeDocument.body.appendChild(innerAppRoot);
56-
copyStyles(document, iframeDocument);
57-
iframeDocument.dir = document.dir;
58-
const syncClassesCleanup = syncClasses(document.body, iframeDocument.body);
59-
mount(<AppComponent />, innerAppRoot);
60-
return () => {
61-
syncClassesCleanup();
62-
unmount(innerAppRoot);
63-
container.removeChild(iframeEl);
64-
};
65-
}, [id, AppComponent]);
57+
const innerAppRoot = iframeDocument.createElement('div');
58+
iframeDocument.body.appendChild(innerAppRoot);
59+
copyStyles(document, iframeDocument);
60+
iframeDocument.dir = document.dir;
61+
const syncClassesCleanup = syncClasses(document.body, iframeDocument.body);
62+
mount(<AppComponent />, innerAppRoot);
63+
cleanupRef.current = () => {
64+
syncClassesCleanup();
65+
unmount(innerAppRoot);
66+
container.removeChild(iframeEl);
67+
};
68+
},
69+
[AppComponent, id]
70+
);
6671

67-
return <div ref={ref}></div>;
72+
return <div ref={mountIframe}></div>;
6873
}

0 commit comments

Comments
 (0)