addons/components/explorer/ComponentExplorer.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {StyleXVar} from '@stylexjs/stylex/lib/StyleXTypes';
b69ab319
b69ab3110import * as stylex from '@stylexjs/stylex';
b69ab3111import {useState, type ReactNode} from 'react';
b69ab3112import {Badge} from '../Badge';
b69ab3113import {Banner, BannerKind} from '../Banner';
b69ab3114import {Button} from '../Button';
b69ab3115import {ButtonDropdown} from '../ButtonDropdown';
b69ab3116import {ButtonGroup} from '../ButtonGroup';
b69ab3117import {Checkbox} from '../Checkbox';
b69ab3118import {Divider} from '../Divider';
b69ab3119import {Dropdown} from '../Dropdown';
b69ab3120import {ErrorNotice} from '../ErrorNotice';
b69ab3121import {HorizontallyGrowingTextField} from '../HorizontallyGrowingTextField';
b69ab3122import {Icon} from '../Icon';
b69ab3123import {Kbd} from '../Kbd';
b69ab3124import {KeyCode, Modifier} from '../KeyboardShortcuts';
b69ab3125import {Panels} from '../Panels';
b69ab3126import {RadioGroup} from '../Radio';
b69ab3127import {Subtle} from '../Subtle';
b69ab3128import {Tag} from '../Tag';
b69ab3129import {TextArea} from '../TextArea';
b69ab3130import {TextField} from '../TextField';
b69ab3131import {Tooltip} from '../Tooltip';
b69ab3132import {Typeahead} from '../Typeahead';
b69ab3133import {layout} from '../theme/layout';
b69ab3134import {colors, font, radius, spacing} from '../theme/tokens.stylex';
b69ab3135
b69ab3136/* eslint-disable no-console */
b69ab3137
b69ab3138const basicBgs = ['bg', 'subtleHoverDarken', 'hoverDarken'] as const;
b69ab3139const pureColors = ['red', 'yellow', 'orange', 'green', 'blue', 'purple', 'grey'] as const;
b69ab3140const scmColors = ['modifiedFg', 'addedFg', 'removedFg', 'missingFg'] as const;
b69ab3141const signalColors = ['signalGoodBg', 'signalMediumBg', 'signalBadBg'] as const;
b69ab3142const paddings = ['none', 'quarter', 'half', 'pad', 'double', 'xlarge'] as const;
b69ab3143const fontSizes = ['smaller', 'small', 'normal', 'big', 'bigger'] as const;
b69ab3144
b69ab3145export default function ComponentExplorer() {
b69ab3146 const [radioChoice, setRadioChoice] = useState('radio');
b69ab3147 const [checkbox1, setCheckbox1] = useState(false);
b69ab3148 const [checkbox2, setCheckbox2] = useState(true);
b69ab3149 const [dropdownChoice, setDropdownChoice] = useState('B');
b69ab3150 const buttonDropdownOptions = [
b69ab3151 {
b69ab3152 id: 'action 1',
b69ab3153 label: 'Action 1',
b69ab3154 },
b69ab3155 {
b69ab3156 id: 'action 2',
b69ab3157 label: 'Action 2',
b69ab3158 },
b69ab3159 ];
b69ab3160 const [activePanel, setActivePanel] = useState<'fruit' | 'vegetables'>('fruit');
b69ab3161 const [buttonDropdownChoice, setButtonDropdownChoice] = useState(buttonDropdownOptions[0]);
b69ab3162 return (
b69ab3163 <div {...stylex.props(styles.container)}>
b69ab3164 <h2>Component Explorer</h2>
b69ab3165 <div {...stylex.props(styles.container, layout.flexCol, layout.fullWidth)}>
b69ab3166 <GroupName>Colors</GroupName>
b69ab3167 <Row>
b69ab3168 Normal
b69ab3169 <Subtle>Subtle</Subtle>
b69ab3170 </Row>
b69ab3171 <Row>
b69ab3172 {basicBgs.map(name => (
b69ab3173 <ColorBadge fg={colors.fg} bg={colors[name]} key={name}>
b69ab3174 {name}
b69ab3175 </ColorBadge>
b69ab3176 ))}
b69ab3177 </Row>
b69ab3178 <Row>
b69ab3179 {scmColors.map(name => (
b69ab3180 <ColorBadge fg={colors[name]} bg={colors.bg} key={name}>
b69ab3181 <Icon icon="diff-modified" />
b69ab3182 {name}
b69ab3183 </ColorBadge>
b69ab3184 ))}
b69ab3185 </Row>
b69ab3186 <Row>
b69ab3187 {pureColors.map(name => (
b69ab3188 <ColorBadge fg={colors[name]} bg={colors.bg} key={name}>
b69ab3189 {name}
b69ab3190 </ColorBadge>
b69ab3191 ))}
b69ab3192 </Row>
b69ab3193 <Row>
b69ab3194 {pureColors.map(name => (
b69ab3195 <ColorBadge fg={colors.fg} bg={colors[name]} key={name}>
b69ab3196 {name}
b69ab3197 </ColorBadge>
b69ab3198 ))}
b69ab3199 </Row>
b69ab31100 <Row>
b69ab31101 <ColorBadge fg={colors.errorFg} bg={colors.errorBg}>
b69ab31102 Error
b69ab31103 </ColorBadge>
b69ab31104 {signalColors.map(name => (
b69ab31105 <ColorBadge fg={colors.signalFg} bg={colors[name]} key={name}>
b69ab31106 {name}
b69ab31107 </ColorBadge>
b69ab31108 ))}
b69ab31109 </Row>
b69ab31110 <Row>
b69ab31111 <span style={{border: '1px solid var(--focus-border)'}}>Focus border</span>
b69ab31112 <span style={{border: '1px solid var(--contrast-border)'}}>Contrast Border</span>
b69ab31113 <span style={{border: '1px solid var(--contrast-active-border)'}}>
b69ab31114 Contrast Active Border
b69ab31115 </span>
b69ab31116 </Row>
b69ab31117 <Row>
b69ab31118 <Icon icon="info" />
b69ab31119 <Icon icon="pass" color="green" />
b69ab31120 <Icon icon="warning" color="yellow" />
b69ab31121 <Icon icon="error" color="red" />
b69ab31122 <Icon icon="lightbulb" color="blue" />
b69ab31123 </Row>
b69ab31124 <Row>
b69ab31125 XS:
b69ab31126 <Icon icon="rocket" size="XS" />
b69ab31127 <span> </span>
b69ab31128 S: (default)
b69ab31129 <Icon icon="rocket" size="S" />
b69ab31130 <span> </span>
b69ab31131 M:
b69ab31132 <Icon icon="rocket" size="M" />
b69ab31133 <span> </span>
b69ab31134 L:
b69ab31135 <Icon icon="rocket" size="L" />
b69ab31136 </Row>
b69ab31137 <GroupName>Components</GroupName>
b69ab31138 <Row>
b69ab31139 <Button primary>Primary</Button>
b69ab31140 <Button disabled primary>
b69ab31141 Primary
b69ab31142 </Button>
b69ab31143 <Button>Secondary</Button>
b69ab31144 <Button disabled>Secondary</Button>
b69ab31145 <Button icon>Icon</Button>
b69ab31146 <Button icon>
b69ab31147 <Icon icon="rocket" />
b69ab31148 Icon
b69ab31149 </Button>
b69ab31150 <Button icon>
b69ab31151 <Icon icon="rocket" />
b69ab31152 </Button>
b69ab31153 <Button icon disabled>
b69ab31154 <Icon icon="rocket" /> Icon
b69ab31155 </Button>
b69ab31156 <Button>
b69ab31157 <Icon icon="rocket" /> Secondary With Icon
b69ab31158 </Button>
b69ab31159 </Row>
b69ab31160 <Row>
b69ab31161 <Dropdown
b69ab31162 options={['Dropdown', 'Option']}
b69ab31163 onChange={e => console.log(e.currentTarget.value)}
b69ab31164 />
b69ab31165 <Dropdown
b69ab31166 disabled
b69ab31167 options={[
b69ab31168 {value: 'none', name: 'Disabled Option', disabled: true},
b69ab31169 {value: 'drop', name: 'Dropdown'},
b69ab31170 {value: 'opt', name: 'Option'},
b69ab31171 ]}
b69ab31172 onChange={e => console.log(e.currentTarget.value)}
b69ab31173 />
b69ab31174 <Dropdown
b69ab31175 value={dropdownChoice}
b69ab31176 onChange={e => setDropdownChoice(e.currentTarget.value)}
b69ab31177 options={['A', 'B', 'C']}
b69ab31178 />
b69ab31179 </Row>
b69ab31180 <Row>
b69ab31181 <ButtonDropdown
b69ab31182 icon={<Icon icon="rocket" />}
b69ab31183 options={buttonDropdownOptions}
b69ab31184 selected={buttonDropdownChoice}
b69ab31185 onClick={selected => console.log('click!', selected)}
b69ab31186 onChangeSelected={setButtonDropdownChoice}
b69ab31187 />
b69ab31188 <ButtonDropdown
b69ab31189 options={buttonDropdownOptions}
b69ab31190 buttonDisabled
b69ab31191 selected={buttonDropdownChoice}
b69ab31192 onClick={selected => console.log('click!', selected)}
b69ab31193 onChangeSelected={setButtonDropdownChoice}
b69ab31194 />
b69ab31195 <ButtonDropdown
b69ab31196 options={buttonDropdownOptions}
b69ab31197 pickerDisabled
b69ab31198 selected={buttonDropdownChoice}
b69ab31199 onClick={selected => console.log('click!', selected)}
b69ab31200 onChangeSelected={setButtonDropdownChoice}
b69ab31201 />
b69ab31202 <ButtonDropdown
b69ab31203 icon={<Icon icon="rocket" />}
b69ab31204 kind="icon"
b69ab31205 options={buttonDropdownOptions}
b69ab31206 selected={buttonDropdownChoice}
b69ab31207 onClick={selected => console.log('click!', selected)}
b69ab31208 onChangeSelected={setButtonDropdownChoice}
b69ab31209 />
b69ab31210 <ButtonDropdown
b69ab31211 kind="icon"
b69ab31212 options={buttonDropdownOptions}
b69ab31213 buttonDisabled
b69ab31214 selected={buttonDropdownChoice}
b69ab31215 onClick={selected => console.log('click!', selected)}
b69ab31216 onChangeSelected={setButtonDropdownChoice}
b69ab31217 />
b69ab31218 <ButtonDropdown
b69ab31219 kind="icon"
b69ab31220 options={buttonDropdownOptions}
b69ab31221 pickerDisabled
b69ab31222 selected={buttonDropdownChoice}
b69ab31223 onClick={selected => console.log('click!', selected)}
b69ab31224 onChangeSelected={setButtonDropdownChoice}
b69ab31225 />
b69ab31226 </Row>
b69ab31227 <Row>
b69ab31228 <ButtonGroup>
b69ab31229 <Button>A</Button>
b69ab31230 <Tooltip title="Wrapping in a tooltip doesn't affect the group styling">
b69ab31231 <Button>B</Button>
b69ab31232 </Tooltip>
b69ab31233 <Button>
b69ab31234 <Icon icon="close" />
b69ab31235 </Button>
b69ab31236 </ButtonGroup>
b69ab31237 <ButtonGroup
b69ab31238 icon /* Be sure to set icon=True on the group if the buttons are icon=True */
b69ab31239 >
b69ab31240 <Button icon style={{paddingInline: '5px'}}>
b69ab31241 Action A
b69ab31242 </Button>
b69ab31243 <Button icon style={{paddingInline: '5px'}}>
b69ab31244 Action B
b69ab31245 </Button>
b69ab31246 <Button icon>
b69ab31247 <Icon icon="close" />
b69ab31248 </Button>
b69ab31249 </ButtonGroup>
b69ab31250 <ButtonGroup>
b69ab31251 <Button>A</Button>
b69ab31252 <Button disabled>B</Button>
b69ab31253 <Button>C</Button>
b69ab31254 <Button primary>D</Button>
b69ab31255 <Button>
b69ab31256 <Icon icon="close" />
b69ab31257 </Button>
b69ab31258 </ButtonGroup>
b69ab31259 </Row>
b69ab31260 <Row>
b69ab31261 <Checkbox checked={checkbox1} onChange={setCheckbox1}>
b69ab31262 Checkbox
b69ab31263 </Checkbox>
b69ab31264 <Checkbox checked={checkbox2} onChange={setCheckbox2}>
b69ab31265 Checked
b69ab31266 </Checkbox>
b69ab31267 <Checkbox checked={false} indeterminate onChange={console.log}>
b69ab31268 Indeterminate
b69ab31269 </Checkbox>
b69ab31270 <Checkbox checked disabled onChange={setCheckbox1}>
b69ab31271 Disabled
b69ab31272 </Checkbox>
b69ab31273 <RadioGroup
b69ab31274 choices={[
b69ab31275 {title: 'Radio', value: 'radio'},
b69ab31276 {title: 'Another', value: 'another'},
b69ab31277 ]}
b69ab31278 current={radioChoice}
b69ab31279 onChange={setRadioChoice}
b69ab31280 />
b69ab31281 </Row>
b69ab31282 <Row>
b69ab31283 Kbd:
b69ab31284 <Kbd keycode={KeyCode.A} modifiers={[Modifier.CMD]} />
b69ab31285 </Row>
b69ab31286 <Row>
b69ab31287 <Badge>Badge</Badge>
b69ab31288 <Badge>0</Badge>
b69ab31289 <Tag>Tag</Tag>
b69ab31290 <Tag>0</Tag>
b69ab31291 {/* <Link href={'#'}>Link</Link> */}
b69ab31292 <Icon icon="loading" />
b69ab31293 Loading
b69ab31294 </Row>
b69ab31295 <Divider />
b69ab31296 <Row>
b69ab31297 <TextArea placeholder="placeholder" onChange={e => console.log(e.currentTarget.value)}>
b69ab31298 Text area
b69ab31299 </TextArea>
b69ab31300 <TextField placeholder="placeholder" onChange={e => console.log(e.currentTarget.value)}>
b69ab31301 Text Field
b69ab31302 </TextField>
b69ab31303 <Tooltip trigger="manual" shouldShow={true} title="Tooltip" placement="bottom">
b69ab31304 Thing
b69ab31305 </Tooltip>
b69ab31306 </Row>
b69ab31307 <Row>
b69ab31308 <HorizontallyGrowingTextField
b69ab31309 placeholder="grows as you type"
b69ab31310 onInput={e => console.log(e.currentTarget.value)}
b69ab31311 />
b69ab31312 </Row>
b69ab31313 <Row>
b69ab31314 <span>Typeahead:</span>
b69ab31315 <ExampleTypeahead />
b69ab31316 </Row>
b69ab31317
b69ab31318 <Row>
b69ab31319 <Banner>Banner</Banner>
b69ab31320 <Banner kind={BannerKind.warning}>Warning Banner</Banner>
b69ab31321 <Banner kind={BannerKind.error}>Error Banner</Banner>
b69ab31322 <Banner icon={<Icon icon="info" />}>Icon Banner</Banner>
b69ab31323 </Row>
b69ab31324 <Row>
b69ab31325 <ErrorNotice
b69ab31326 title="Error Notice"
b69ab31327 description="description"
b69ab31328 details="details / stack trace"
b69ab31329 />
b69ab31330 </Row>
b69ab31331 <Row>
b69ab31332 <Panels
b69ab31333 active={activePanel}
b69ab31334 panels={{
b69ab31335 fruit: {label: 'Fruit', render: () => <div>Apple</div>},
b69ab31336 vegetables: {label: 'Vegetables', render: () => <div>Broccoli</div>},
b69ab31337 }}
b69ab31338 onSelect={setActivePanel}
b69ab31339 />
b69ab31340 </Row>
b69ab31341 <GroupName>Spacing</GroupName>
b69ab31342 <Row>
b69ab31343 {paddings.map(size => (
b69ab31344 <ColorBadge style={styles.padding(size)} key={size}>
b69ab31345 {size}
b69ab31346 </ColorBadge>
b69ab31347 ))}
b69ab31348 </Row>
b69ab31349 <Row>
b69ab31350 <div {...stylex.props(layout.flexCol)} style={{alignItems: 'flex-start'}}>
b69ab31351 {paddings.map(size => (
b69ab31352 <div {...stylex.props(layout.flexRow)} style={{gap: spacing[size]}} key={size}>
b69ab31353 <ColorBadge>A</ColorBadge>
b69ab31354 <ColorBadge>B</ColorBadge>
b69ab31355 <ColorBadge>{size}</ColorBadge>
b69ab31356 </div>
b69ab31357 ))}
b69ab31358 </div>
b69ab31359 </Row>
b69ab31360 <GroupName>Font</GroupName>
b69ab31361 <Row>
b69ab31362 {fontSizes.map(size => (
b69ab31363 <ColorBadge style={styles.font(size)} bg={colors.hoverDarken} key={size}>
b69ab31364 {size}
b69ab31365 </ColorBadge>
b69ab31366 ))}
b69ab31367 </Row>
b69ab31368 </div>
b69ab31369 </div>
b69ab31370 );
b69ab31371}
b69ab31372
b69ab31373const styles = stylex.create({
b69ab31374 container: {
b69ab31375 padding: spacing.pad,
b69ab31376 overflow: 'auto',
b69ab31377 },
b69ab31378 badge: (fg, bg) => ({
b69ab31379 backgroundColor: bg,
b69ab31380 color: fg,
b69ab31381 fontFamily: 'monospace',
b69ab31382 paddingBlock: spacing.half,
b69ab31383 paddingInline: spacing.pad,
b69ab31384 borderRadius: radius.round,
b69ab31385 }),
b69ab31386 groupName: {
b69ab31387 fontSize: font.bigger,
b69ab31388 width: '100%',
b69ab31389 paddingTop: spacing.double,
b69ab31390 fontWeight: 'bold',
b69ab31391 },
b69ab31392 padding: (pad: (typeof paddings)[number]) => ({
b69ab31393 padding: spacing[pad],
b69ab31394 }),
b69ab31395 font: (size: (typeof fontSizes)[number]) => ({
b69ab31396 fontSize: font[size],
b69ab31397 }),
b69ab31398});
b69ab31399
b69ab31400function ColorBadge({
b69ab31401 children,
b69ab31402 bg,
b69ab31403 fg,
b69ab31404 style,
b69ab31405}: {
b69ab31406 children: ReactNode;
b69ab31407 bg?: StyleXVar<string>;
b69ab31408 fg?: StyleXVar<string>;
b69ab31409 style?: stylex.StyleXStyles;
b69ab31410}) {
b69ab31411 return (
b69ab31412 <div {...stylex.props(layout.flexRow, styles.badge(fg, bg ?? colors.hoverDarken), style)}>
b69ab31413 {children}
b69ab31414 </div>
b69ab31415 );
b69ab31416}
b69ab31417
b69ab31418function Row({children, style}: {children: ReactNode; style?: stylex.StyleXStyles}) {
b69ab31419 return <div {...stylex.props(layout.flexRow, layout.fullWidth, style)}>{children}</div>;
b69ab31420}
b69ab31421
b69ab31422function GroupName({children}: {children: ReactNode}) {
b69ab31423 return <div {...stylex.props(styles.groupName)}>{children}</div>;
b69ab31424}
b69ab31425
b69ab31426function ExampleTypeahead() {
b69ab31427 const [value, setValue] = useState('');
b69ab31428
b69ab31429 const possibleValues = [
b69ab31430 'apple',
b69ab31431 'banana',
b69ab31432 'cherry',
b69ab31433 'date',
b69ab31434 'elderberry',
b69ab31435 'fig',
b69ab31436 'grape',
b69ab31437 'honeydew',
b69ab31438 'jackfruit',
b69ab31439 'kiwi',
b69ab31440 ];
b69ab31441 const fetchTokens = async (searchTerm: string) => {
b69ab31442 await new Promise(resolve => setTimeout(resolve, 500));
b69ab31443 return {
b69ab31444 values: possibleValues
b69ab31445 .filter(v => v.includes(searchTerm))
b69ab31446 .map(value => ({
b69ab31447 label: value,
b69ab31448 value,
b69ab31449 })),
b69ab31450 fetchStartTimestamp: Date.now(),
b69ab31451 };
b69ab31452 };
b69ab31453 return (
b69ab31454 <Typeahead
b69ab31455 tokenString={value}
b69ab31456 setTokenString={setValue}
b69ab31457 fetchTokens={fetchTokens}
b69ab31458 autoFocus={false}
b69ab31459 maxTokens={3}
b69ab31460 />
b69ab31461 );
b69ab31462}