documentation-site/components/yard/knob.tsx (236 lines of code) (raw):

/* Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ import * as React from "react"; import { useStyletron } from "baseui"; import { StyledLink } from "baseui/link"; import { Input } from "baseui/input"; import { Radio, RadioGroup } from "baseui/radio"; import { Checkbox } from "baseui/checkbox"; import { Select, SIZE } from "baseui/select"; import { StatefulTooltip } from "baseui/tooltip"; import Editor from "./editor"; import type { TPropValue } from "react-view"; import { assertUnreachable, useValueDebounce, PropTypes, Error, } from "react-view"; const getTooltip = (description: string, type: string, name: string) => ( <span> <p> <b>{name}</b>: <i>{type}</i> </p> <p>{description}</p> </span> ); const Spacing: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [css, theme] = useStyletron(); return ( <div className={css({ margin: `${theme.sizing.scale400} 0` })}> {children} </div> ); }; const Label: React.FC<{ children: React.ReactNode; tooltip: React.ReactNode; }> = ({ children, tooltip }) => { const [css, theme] = useStyletron(); return ( // eslint-disable-next-line jsx-a11y/label-has-associated-control <label className={css({ ...theme.typography.font250, color: theme.colors.contentPrimary, })} > <StatefulTooltip accessibilityType="tooltip" content={tooltip}> <span className={css({ textDecoration: "underline", ":hover": { color: theme.colors.contentSecondary, }, })} > {children} </span> </StatefulTooltip> </label> ); }; const Knob: React.SFC<{ name: string; error: string | null; description: string; val: TPropValue; set: (val: TPropValue) => void; type: PropTypes; options?: { [key: string]: string }; placeholder?: string; enumName?: string; }> = ({ name, error, type, val: globalVal, set: globalSet, options = {}, description, placeholder, enumName, }) => { const [val, set] = useValueDebounce<TPropValue>(globalVal, globalSet); const [css, theme] = useStyletron(); switch (type) { case PropTypes.Ref: return ( <Spacing> <Label tooltip={getTooltip(description, type, name)}>{name}</Label> <StyledLink href="https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom" target="_blank" $style={{ fontSize: "14px", display: "block", }} > React Ref documentation </StyledLink> <Error msg={error} isPopup /> </Spacing> ); case PropTypes.String: case PropTypes.Date: case PropTypes.Number: return ( <Spacing> <Label tooltip={getTooltip(description, type, name)}>{name}</Label> <Input error={Boolean(error)} onChange={(event) => set((event.target as HTMLInputElement).value)} placeholder={placeholder} size="compact" value={val ? String(val) : undefined} /> <Error msg={error} isPopup /> </Spacing> ); case PropTypes.Boolean: return ( <Spacing> <Checkbox checked={Boolean(val)} onChange={() => { globalSet(!val); }} > <StatefulTooltip accessibilityType="tooltip" content={getTooltip(description, type, name)} placement="right" > <span className={css({ textDecoration: "underline", ":hover": { color: theme.colors.contentSecondary, }, })} > {name} </span> </StatefulTooltip> </Checkbox> <Error msg={error} isPopup /> </Spacing> ); case PropTypes.Enum: // eslint-disable-next-line no-case-declarations const optionsKeys = Object.keys(options); // eslint-disable-next-line no-case-declarations const numberOfOptions = optionsKeys.length; // eslint-disable-next-line no-case-declarations const selectOptions = optionsKeys.map((key) => ({ id: key, option: options[key], })); // eslint-disable-next-line no-case-declarations const valueKey = val && String(val).split(".")[1]; return ( <Spacing> <Label tooltip={getTooltip(description, type, name)}>{name}</Label> {numberOfOptions < 7 ? ( <RadioGroup name={`radio-${name}`} align="horizontal" overrides={{ RadioGroupRoot: { style: ({ $theme }) => ({ flexWrap: "wrap", marginTop: 0, marginBottom: $theme.sizing.scale300, }), }, }} onChange={(e) => { globalSet((e.target as HTMLInputElement).value); }} value={String(val)} > {Object.keys(options).map((opt) => ( <Radio key={opt} value={`${enumName || name.toUpperCase()}.${opt}`} overrides={{ Root: { style: ({ $theme }) => ({ marginRight: $theme.sizing.scale600, marginTop: 0, marginBottom: 0, }), }, Label: { style: ({ $theme }) => $theme.typography.font250, }, }} > {opt} </Radio> ))} </RadioGroup> ) : ( <Select size={SIZE.compact} options={selectOptions} clearable={false} value={[{ id: valueKey || "", option: valueKey }]} labelKey="option" valueKey="id" onChange={({ value }) => { globalSet(`${enumName || name.toUpperCase()}.${value[0].id}`); }} /> )} <Error msg={error} isPopup /> </Spacing> ); case PropTypes.ReactNode: case PropTypes.Function: case PropTypes.Array: case PropTypes.Object: return ( <Spacing> <Label tooltip={getTooltip(description, type, name)}>{name}</Label> <Editor onChange={(code) => { globalSet(code); }} code={val ? String(val) : ""} placeholder={placeholder} small /> <Error msg={error} isPopup /> </Spacing> ); case PropTypes.Custom: return null; default: return assertUnreachable(); } }; export default Knob;