14.0 KB463 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {StyleXVar} from '@stylexjs/stylex/lib/StyleXTypes';
9
10import * as stylex from '@stylexjs/stylex';
11import {useState, type ReactNode} from 'react';
12import {Badge} from '../Badge';
13import {Banner, BannerKind} from '../Banner';
14import {Button} from '../Button';
15import {ButtonDropdown} from '../ButtonDropdown';
16import {ButtonGroup} from '../ButtonGroup';
17import {Checkbox} from '../Checkbox';
18import {Divider} from '../Divider';
19import {Dropdown} from '../Dropdown';
20import {ErrorNotice} from '../ErrorNotice';
21import {HorizontallyGrowingTextField} from '../HorizontallyGrowingTextField';
22import {Icon} from '../Icon';
23import {Kbd} from '../Kbd';
24import {KeyCode, Modifier} from '../KeyboardShortcuts';
25import {Panels} from '../Panels';
26import {RadioGroup} from '../Radio';
27import {Subtle} from '../Subtle';
28import {Tag} from '../Tag';
29import {TextArea} from '../TextArea';
30import {TextField} from '../TextField';
31import {Tooltip} from '../Tooltip';
32import {Typeahead} from '../Typeahead';
33import {layout} from '../theme/layout';
34import {colors, font, radius, spacing} from '../theme/tokens.stylex';
35
36/* eslint-disable no-console */
37
38const basicBgs = ['bg', 'subtleHoverDarken', 'hoverDarken'] as const;
39const pureColors = ['red', 'yellow', 'orange', 'green', 'blue', 'purple', 'grey'] as const;
40const scmColors = ['modifiedFg', 'addedFg', 'removedFg', 'missingFg'] as const;
41const signalColors = ['signalGoodBg', 'signalMediumBg', 'signalBadBg'] as const;
42const paddings = ['none', 'quarter', 'half', 'pad', 'double', 'xlarge'] as const;
43const fontSizes = ['smaller', 'small', 'normal', 'big', 'bigger'] as const;
44
45export default function ComponentExplorer() {
46 const [radioChoice, setRadioChoice] = useState('radio');
47 const [checkbox1, setCheckbox1] = useState(false);
48 const [checkbox2, setCheckbox2] = useState(true);
49 const [dropdownChoice, setDropdownChoice] = useState('B');
50 const buttonDropdownOptions = [
51 {
52 id: 'action 1',
53 label: 'Action 1',
54 },
55 {
56 id: 'action 2',
57 label: 'Action 2',
58 },
59 ];
60 const [activePanel, setActivePanel] = useState<'fruit' | 'vegetables'>('fruit');
61 const [buttonDropdownChoice, setButtonDropdownChoice] = useState(buttonDropdownOptions[0]);
62 return (
63 <div {...stylex.props(styles.container)}>
64 <h2>Component Explorer</h2>
65 <div {...stylex.props(styles.container, layout.flexCol, layout.fullWidth)}>
66 <GroupName>Colors</GroupName>
67 <Row>
68 Normal
69 <Subtle>Subtle</Subtle>
70 </Row>
71 <Row>
72 {basicBgs.map(name => (
73 <ColorBadge fg={colors.fg} bg={colors[name]} key={name}>
74 {name}
75 </ColorBadge>
76 ))}
77 </Row>
78 <Row>
79 {scmColors.map(name => (
80 <ColorBadge fg={colors[name]} bg={colors.bg} key={name}>
81 <Icon icon="diff-modified" />
82 {name}
83 </ColorBadge>
84 ))}
85 </Row>
86 <Row>
87 {pureColors.map(name => (
88 <ColorBadge fg={colors[name]} bg={colors.bg} key={name}>
89 {name}
90 </ColorBadge>
91 ))}
92 </Row>
93 <Row>
94 {pureColors.map(name => (
95 <ColorBadge fg={colors.fg} bg={colors[name]} key={name}>
96 {name}
97 </ColorBadge>
98 ))}
99 </Row>
100 <Row>
101 <ColorBadge fg={colors.errorFg} bg={colors.errorBg}>
102 Error
103 </ColorBadge>
104 {signalColors.map(name => (
105 <ColorBadge fg={colors.signalFg} bg={colors[name]} key={name}>
106 {name}
107 </ColorBadge>
108 ))}
109 </Row>
110 <Row>
111 <span style={{border: '1px solid var(--focus-border)'}}>Focus border</span>
112 <span style={{border: '1px solid var(--contrast-border)'}}>Contrast Border</span>
113 <span style={{border: '1px solid var(--contrast-active-border)'}}>
114 Contrast Active Border
115 </span>
116 </Row>
117 <Row>
118 <Icon icon="info" />
119 <Icon icon="pass" color="green" />
120 <Icon icon="warning" color="yellow" />
121 <Icon icon="error" color="red" />
122 <Icon icon="lightbulb" color="blue" />
123 </Row>
124 <Row>
125 XS:
126 <Icon icon="rocket" size="XS" />
127 <span> </span>
128 S: (default)
129 <Icon icon="rocket" size="S" />
130 <span> </span>
131 M:
132 <Icon icon="rocket" size="M" />
133 <span> </span>
134 L:
135 <Icon icon="rocket" size="L" />
136 </Row>
137 <GroupName>Components</GroupName>
138 <Row>
139 <Button primary>Primary</Button>
140 <Button disabled primary>
141 Primary
142 </Button>
143 <Button>Secondary</Button>
144 <Button disabled>Secondary</Button>
145 <Button icon>Icon</Button>
146 <Button icon>
147 <Icon icon="rocket" />
148 Icon
149 </Button>
150 <Button icon>
151 <Icon icon="rocket" />
152 </Button>
153 <Button icon disabled>
154 <Icon icon="rocket" /> Icon
155 </Button>
156 <Button>
157 <Icon icon="rocket" /> Secondary With Icon
158 </Button>
159 </Row>
160 <Row>
161 <Dropdown
162 options={['Dropdown', 'Option']}
163 onChange={e => console.log(e.currentTarget.value)}
164 />
165 <Dropdown
166 disabled
167 options={[
168 {value: 'none', name: 'Disabled Option', disabled: true},
169 {value: 'drop', name: 'Dropdown'},
170 {value: 'opt', name: 'Option'},
171 ]}
172 onChange={e => console.log(e.currentTarget.value)}
173 />
174 <Dropdown
175 value={dropdownChoice}
176 onChange={e => setDropdownChoice(e.currentTarget.value)}
177 options={['A', 'B', 'C']}
178 />
179 </Row>
180 <Row>
181 <ButtonDropdown
182 icon={<Icon icon="rocket" />}
183 options={buttonDropdownOptions}
184 selected={buttonDropdownChoice}
185 onClick={selected => console.log('click!', selected)}
186 onChangeSelected={setButtonDropdownChoice}
187 />
188 <ButtonDropdown
189 options={buttonDropdownOptions}
190 buttonDisabled
191 selected={buttonDropdownChoice}
192 onClick={selected => console.log('click!', selected)}
193 onChangeSelected={setButtonDropdownChoice}
194 />
195 <ButtonDropdown
196 options={buttonDropdownOptions}
197 pickerDisabled
198 selected={buttonDropdownChoice}
199 onClick={selected => console.log('click!', selected)}
200 onChangeSelected={setButtonDropdownChoice}
201 />
202 <ButtonDropdown
203 icon={<Icon icon="rocket" />}
204 kind="icon"
205 options={buttonDropdownOptions}
206 selected={buttonDropdownChoice}
207 onClick={selected => console.log('click!', selected)}
208 onChangeSelected={setButtonDropdownChoice}
209 />
210 <ButtonDropdown
211 kind="icon"
212 options={buttonDropdownOptions}
213 buttonDisabled
214 selected={buttonDropdownChoice}
215 onClick={selected => console.log('click!', selected)}
216 onChangeSelected={setButtonDropdownChoice}
217 />
218 <ButtonDropdown
219 kind="icon"
220 options={buttonDropdownOptions}
221 pickerDisabled
222 selected={buttonDropdownChoice}
223 onClick={selected => console.log('click!', selected)}
224 onChangeSelected={setButtonDropdownChoice}
225 />
226 </Row>
227 <Row>
228 <ButtonGroup>
229 <Button>A</Button>
230 <Tooltip title="Wrapping in a tooltip doesn't affect the group styling">
231 <Button>B</Button>
232 </Tooltip>
233 <Button>
234 <Icon icon="close" />
235 </Button>
236 </ButtonGroup>
237 <ButtonGroup
238 icon /* Be sure to set icon=True on the group if the buttons are icon=True */
239 >
240 <Button icon style={{paddingInline: '5px'}}>
241 Action A
242 </Button>
243 <Button icon style={{paddingInline: '5px'}}>
244 Action B
245 </Button>
246 <Button icon>
247 <Icon icon="close" />
248 </Button>
249 </ButtonGroup>
250 <ButtonGroup>
251 <Button>A</Button>
252 <Button disabled>B</Button>
253 <Button>C</Button>
254 <Button primary>D</Button>
255 <Button>
256 <Icon icon="close" />
257 </Button>
258 </ButtonGroup>
259 </Row>
260 <Row>
261 <Checkbox checked={checkbox1} onChange={setCheckbox1}>
262 Checkbox
263 </Checkbox>
264 <Checkbox checked={checkbox2} onChange={setCheckbox2}>
265 Checked
266 </Checkbox>
267 <Checkbox checked={false} indeterminate onChange={console.log}>
268 Indeterminate
269 </Checkbox>
270 <Checkbox checked disabled onChange={setCheckbox1}>
271 Disabled
272 </Checkbox>
273 <RadioGroup
274 choices={[
275 {title: 'Radio', value: 'radio'},
276 {title: 'Another', value: 'another'},
277 ]}
278 current={radioChoice}
279 onChange={setRadioChoice}
280 />
281 </Row>
282 <Row>
283 Kbd:
284 <Kbd keycode={KeyCode.A} modifiers={[Modifier.CMD]} />
285 </Row>
286 <Row>
287 <Badge>Badge</Badge>
288 <Badge>0</Badge>
289 <Tag>Tag</Tag>
290 <Tag>0</Tag>
291 {/* <Link href={'#'}>Link</Link> */}
292 <Icon icon="loading" />
293 Loading
294 </Row>
295 <Divider />
296 <Row>
297 <TextArea placeholder="placeholder" onChange={e => console.log(e.currentTarget.value)}>
298 Text area
299 </TextArea>
300 <TextField placeholder="placeholder" onChange={e => console.log(e.currentTarget.value)}>
301 Text Field
302 </TextField>
303 <Tooltip trigger="manual" shouldShow={true} title="Tooltip" placement="bottom">
304 Thing
305 </Tooltip>
306 </Row>
307 <Row>
308 <HorizontallyGrowingTextField
309 placeholder="grows as you type"
310 onInput={e => console.log(e.currentTarget.value)}
311 />
312 </Row>
313 <Row>
314 <span>Typeahead:</span>
315 <ExampleTypeahead />
316 </Row>
317
318 <Row>
319 <Banner>Banner</Banner>
320 <Banner kind={BannerKind.warning}>Warning Banner</Banner>
321 <Banner kind={BannerKind.error}>Error Banner</Banner>
322 <Banner icon={<Icon icon="info" />}>Icon Banner</Banner>
323 </Row>
324 <Row>
325 <ErrorNotice
326 title="Error Notice"
327 description="description"
328 details="details / stack trace"
329 />
330 </Row>
331 <Row>
332 <Panels
333 active={activePanel}
334 panels={{
335 fruit: {label: 'Fruit', render: () => <div>Apple</div>},
336 vegetables: {label: 'Vegetables', render: () => <div>Broccoli</div>},
337 }}
338 onSelect={setActivePanel}
339 />
340 </Row>
341 <GroupName>Spacing</GroupName>
342 <Row>
343 {paddings.map(size => (
344 <ColorBadge style={styles.padding(size)} key={size}>
345 {size}
346 </ColorBadge>
347 ))}
348 </Row>
349 <Row>
350 <div {...stylex.props(layout.flexCol)} style={{alignItems: 'flex-start'}}>
351 {paddings.map(size => (
352 <div {...stylex.props(layout.flexRow)} style={{gap: spacing[size]}} key={size}>
353 <ColorBadge>A</ColorBadge>
354 <ColorBadge>B</ColorBadge>
355 <ColorBadge>{size}</ColorBadge>
356 </div>
357 ))}
358 </div>
359 </Row>
360 <GroupName>Font</GroupName>
361 <Row>
362 {fontSizes.map(size => (
363 <ColorBadge style={styles.font(size)} bg={colors.hoverDarken} key={size}>
364 {size}
365 </ColorBadge>
366 ))}
367 </Row>
368 </div>
369 </div>
370 );
371}
372
373const styles = stylex.create({
374 container: {
375 padding: spacing.pad,
376 overflow: 'auto',
377 },
378 badge: (fg, bg) => ({
379 backgroundColor: bg,
380 color: fg,
381 fontFamily: 'monospace',
382 paddingBlock: spacing.half,
383 paddingInline: spacing.pad,
384 borderRadius: radius.round,
385 }),
386 groupName: {
387 fontSize: font.bigger,
388 width: '100%',
389 paddingTop: spacing.double,
390 fontWeight: 'bold',
391 },
392 padding: (pad: (typeof paddings)[number]) => ({
393 padding: spacing[pad],
394 }),
395 font: (size: (typeof fontSizes)[number]) => ({
396 fontSize: font[size],
397 }),
398});
399
400function ColorBadge({
401 children,
402 bg,
403 fg,
404 style,
405}: {
406 children: ReactNode;
407 bg?: StyleXVar<string>;
408 fg?: StyleXVar<string>;
409 style?: stylex.StyleXStyles;
410}) {
411 return (
412 <div {...stylex.props(layout.flexRow, styles.badge(fg, bg ?? colors.hoverDarken), style)}>
413 {children}
414 </div>
415 );
416}
417
418function Row({children, style}: {children: ReactNode; style?: stylex.StyleXStyles}) {
419 return <div {...stylex.props(layout.flexRow, layout.fullWidth, style)}>{children}</div>;
420}
421
422function GroupName({children}: {children: ReactNode}) {
423 return <div {...stylex.props(styles.groupName)}>{children}</div>;
424}
425
426function ExampleTypeahead() {
427 const [value, setValue] = useState('');
428
429 const possibleValues = [
430 'apple',
431 'banana',
432 'cherry',
433 'date',
434 'elderberry',
435 'fig',
436 'grape',
437 'honeydew',
438 'jackfruit',
439 'kiwi',
440 ];
441 const fetchTokens = async (searchTerm: string) => {
442 await new Promise(resolve => setTimeout(resolve, 500));
443 return {
444 values: possibleValues
445 .filter(v => v.includes(searchTerm))
446 .map(value => ({
447 label: value,
448 value,
449 })),
450 fetchStartTimestamp: Date.now(),
451 };
452 };
453 return (
454 <Typeahead
455 tokenString={value}
456 setTokenString={setValue}
457 fetchTokens={fetchTokens}
458 autoFocus={false}
459 maxTokens={3}
460 />
461 );
462}
463