Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support 'fullWidth' prop on all text fields #979

Merged
merged 3 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"@homebound/eslint-config": "^1.8.0",
"@homebound/rtl-react-router-utils": "1.0.3",
"@homebound/rtl-utils": "^2.65.0",
"@homebound/truss": "^1.131.0",
"@homebound/truss": "^1.132.0",
"@homebound/tsconfig": "^1.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
Expand Down
16 changes: 16 additions & 0 deletions src/Css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,22 @@ class CssBuilder<T extends Properties> {
fd(value: Properties["flexDirection"]) {
return this.add("flexDirection", value);
}
/** Sets `flexWrap: "wrap"`. */
get fww() {
return this.add("flexWrap", "wrap");
}
/** Sets `flexWrap: "wrap-reverse"`. */
get fwr() {
return this.add("flexWrap", "wrap-reverse");
}
/** Sets `flexWrap: "nowrap"`. */
get fwnw() {
return this.add("flexWrap", "nowrap");
}
/** Sets `flexWrap: value`. */
flexWrap(value: Properties["flexWrap"]) {
return this.add("flexWrap", value);
}

// float
/** Sets `float: "left"`. */
Expand Down
2 changes: 2 additions & 0 deletions src/components/PresentationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface PresentationFieldProps {
visuallyDisabled?: false;
// If set error messages will be rendered as tooltips rather than below the field
errorInTooltip?: true;
/** Allow the fields to grow to the width of its container. By default, fields will extend up to 550px */
fullWidth?: boolean;
}

export type PresentationContextProps = {
Expand Down
104 changes: 97 additions & 7 deletions src/forms/FormLines.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Meta } from "@storybook/react";
import { FieldGroup, FormDivider, FormLines } from "src/forms/FormLines";
import { SelectField } from "src/inputs";
import { DateField, MultiSelectField, RichTextField, SelectField, TextAreaField, TreeSelectField } from "src/inputs";
import { NumberField } from "src/inputs/NumberField";
import { Switch } from "src/inputs/Switch";
import { TextField } from "src/inputs/TextField";
Expand All @@ -13,26 +13,116 @@ export default {
export function FlatList() {
return (
<FormLines>
<TextField label="First" value="first" onChange={() => {}} />
<TextField label="Last" value="last" onChange={() => {}} />
<TextField label="Text Field" value="first" onChange={noop} />
<NumberField label="Number Field" value={1} onChange={noop} />
<DateField label="Date FIeld" value={undefined} onChange={noop} />
<SelectField<Options, number>
label="Single Select Field"
value={1}
options={[
{ id: 1, name: "Green" },
{ id: 2, name: "Red" },
]}
onSelect={noop}
/>
<MultiSelectField<Options, number>
label="Multiselect Field"
values={[1]}
options={[
{ id: 1, name: "Soccer" },
{ id: 2, name: "Basketball" },
{ id: 3, name: "Football" },
]}
onSelect={noop}
/>
<TreeSelectField
values={[]}
onSelect={noop}
options={[{ id: "1", name: "One" }]}
label="Tree Select Field"
getOptionValue={(o) => o.id}
getOptionLabel={(o) => o.name}
/>
<TextAreaField label="Text Area Field" value="" onChange={noop} />
<RichTextField label="Rich Text Field" value="" onChange={noop} />
</FormLines>
);
}

export function SmallFlatList() {
return (
<FormLines width="sm">
<TextField label="First" value="first" onChange={() => {}} />
<TextField label="Last" value="last" onChange={() => {}} />
<TextField label="Text Field" value="first" onChange={noop} />
<NumberField label="Number Field" value={1} onChange={noop} />
<DateField label="Date FIeld" value={undefined} onChange={noop} />
<SelectField<Options, number>
label="Single Select Field"
value={1}
options={[
{ id: 1, name: "Green" },
{ id: 2, name: "Red" },
]}
onSelect={noop}
/>
<MultiSelectField<Options, number>
label="Multiselect Field"
values={[1]}
options={[
{ id: 1, name: "Soccer" },
{ id: 2, name: "Basketball" },
{ id: 3, name: "Football" },
]}
onSelect={noop}
/>
<TreeSelectField
values={[]}
onSelect={noop}
options={[{ id: "1", name: "One" }]}
label="Tree Select Field"
getOptionValue={(o) => o.id}
getOptionLabel={(o) => o.name}
/>
<TextAreaField label="Text Area Field" value="" onChange={noop} />
<RichTextField label="Rich Text Field" value="" onChange={noop} />
</FormLines>
);
}

export function FullFlatList() {
return (
<FormLines width="full">
<TextField label="First" value="first" onChange={() => {}} />
<TextField label="Last" value="last" onChange={() => {}} />
<TextField label="Text Field" value="first" onChange={noop} />
<NumberField label="Number Field" value={1} onChange={noop} />
<DateField label="Date FIeld" value={undefined} onChange={noop} />
<SelectField<Options, number>
label="Single Select Field"
value={1}
options={[
{ id: 1, name: "Green" },
{ id: 2, name: "Red" },
]}
onSelect={noop}
/>
<MultiSelectField<Options, number>
label="Multiselect Field"
values={[1]}
options={[
{ id: 1, name: "Soccer" },
{ id: 2, name: "Basketball" },
{ id: 3, name: "Football" },
]}
onSelect={noop}
/>
<TreeSelectField
values={[]}
onSelect={noop}
options={[{ id: "1", name: "One" }]}
label="Tree Select Field"
getOptionValue={(o) => o.id}
getOptionLabel={(o) => o.name}
/>
<TextAreaField label="Text Area Field" value="" onChange={noop} />
<RichTextField label="Rich Text Field" value="" onChange={noop} />
</FormLines>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/forms/FormLines.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ export interface FormLinesProps {
* (see the `FieldGroup` component), where they will be laid out side-by-side.
*/
export function FormLines(props: FormLinesProps) {
const { children, width = "full", labelSuffix, labelStyle, compact } = props;
const { children, width = "lg", labelSuffix, labelStyle, compact } = props;
let firstFormHeading = true;

// Only overwrite `fieldProps` if new values are explicitly set. Ensures we only set to `undefined` if explicitly set.
const newFieldProps = {
...("labelSuffix" in props ? { labelSuffix } : {}),
...("labelStyle" in props ? { labelStyle } : {}),
...("compact" in props ? { compact } : {}),
...(width === "full" ? { fullWidth: true } : {}),
};

return (
Expand Down
14 changes: 9 additions & 5 deletions src/inputs/Autocomplete.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@ import { within } from "@storybook/testing-library";
import { useState } from "react";
import { useFilter } from "react-aria";
import { Css } from "src/Css";
import { Autocomplete } from "src/inputs/Autocomplete";
import { Autocomplete, AutocompleteProps } from "src/inputs/Autocomplete";

export default {
component: Autocomplete,
} as Meta;

export const Example: StoryFn = Template.bind({});
export const Example: StoryFn<AutocompleteProps<any>> = Template.bind({});
Example.parameters = { chromatic: { disableSnapshot: true } };

export const MenuOpen: StoryFn = Template.bind({});
export const FullWidth: StoryFn<AutocompleteProps<any>> = Template.bind({});
FullWidth.args = { fullWidth: true };

export const MenuOpen: StoryFn<AutocompleteProps<any>> = Template.bind({});
MenuOpen.play = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
const canvas = within(canvasElement);
canvas.getByTestId("heroes").focus();
};

function Template() {
function Template(props: AutocompleteProps<any>) {
const { contains } = useFilter({ sensitivity: "base" });
const [value, setValue] = useState<string>();
const allOptions = [
Expand All @@ -30,9 +33,10 @@ function Template() {
{ label: "Black Widow", imgSrc: "/black-widow.jpg" },
];
const [options, setOptions] = useState(allOptions);

console.log("props", props);
return (
<Autocomplete<TestOption>
{...props}
label="Heroes"
labelStyle="hidden"
getOptionValue={(o) => o.label}
Expand Down
4 changes: 2 additions & 2 deletions src/inputs/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { ListBox } from "src/inputs/internal/ListBox";
import { TextFieldBase, TextFieldBaseProps } from "src/inputs/TextFieldBase";
import { Value, valueToKey } from "src/inputs/Value";

interface AutocompleteProps<T>
export interface AutocompleteProps<T>
extends Pick<PresentationFieldProps, "labelStyle">,
Pick<TextFieldBaseProps<any>, "label" | "clearable" | "startAdornment"> {
Pick<TextFieldBaseProps<any>, "label" | "clearable" | "startAdornment" | "fullWidth"> {
onSelect: (item: T) => void;
/** A function that returns how to render the an option in the menu. If not set, `getOptionLabel` will be used */
getOptionMenuLabel?: (o: T) => ReactNode;
Expand Down
4 changes: 2 additions & 2 deletions src/inputs/DateFields/DateField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import { Css } from "src/Css";
import { jan1, jan10, jan2 } from "src/forms/formStateDomain";
import { DateField, DateFieldProps, TextField } from "src/inputs/index";
import { noop } from "src/utils";
import { samples, withDimensions } from "src/utils/sb";
import { samples } from "src/utils/sb";

export default {
component: Button,
decorators: [withDimensions()],
parameters: {
design: {
type: "figma",
Expand Down Expand Up @@ -61,6 +60,7 @@ export function DateFields() {
disabledDays={[{ before: jan1 }, { after: jan10 }]}
/>,
],
["Full Width", <TestDateField label="Date" fullWidth />],
);
}

Expand Down
5 changes: 4 additions & 1 deletion src/inputs/DateFields/DateFieldBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import { maybeCall, useTestIds } from "src/utils";
import { defaultTestId } from "src/utils/defaultTestId";

export interface DateFieldBaseProps
extends Pick<TextFieldBaseProps<Properties>, "borderless" | "visuallyDisabled" | "labelStyle" | "compact"> {
extends Pick<
TextFieldBaseProps<Properties>,
"borderless" | "visuallyDisabled" | "labelStyle" | "compact" | "fullWidth"
> {
label: string;
/** Called when the component loses focus */
onBlur?: () => void;
Expand Down
1 change: 1 addition & 0 deletions src/inputs/DateFields/DateRangeField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function Example() {
label="Error message"
errorMsg={rangeInitUndefined ? undefined : "Required"}
/>
<DateRangeField {...commonProps} fullWidth label="Full Width Field" />
</div>
);
}
5 changes: 5 additions & 0 deletions src/inputs/MultiSelectField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ export function MultiSelectFields() {
)}
/>
</div>

<div css={Css.df.fdc.gap2.$}>
<h1 css={Css.lg.$}>Full Width</h1>
<TestMultiSelectField fullWidth label="Favorite Icon" values={[options[2].id]} options={options} />
</div>
</div>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/inputs/NumberField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ export function NumberFieldStyles() {
<h1 css={Css.lg.$}>Without grouping</h1>
<TestNumberField value={123456789} label="No grouping" useGrouping={false} />
</div>

<div css={Css.df.fdc.gap2.$}>
<h1 css={Css.lg.$}>Full Width</h1>
<TestNumberField value={0} label="Age" fullWidth />
</div>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/inputs/NumberField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { TextFieldBase } from "./TextFieldBase";
export type NumberFieldType = "cents" | "dollars" | "percent" | "basisPoints" | "days";

// exported for testing purposes
export interface NumberFieldProps extends Pick<PresentationFieldProps, "labelStyle"> {
export interface NumberFieldProps extends Pick<PresentationFieldProps, "labelStyle" | "fullWidth"> {
label: string;
/** If set, the label will be defined as 'aria-label` on the input element */
type?: NumberFieldType;
Expand Down
17 changes: 13 additions & 4 deletions src/inputs/RichTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import Tribute from "tributejs";
import "tributejs/dist/tribute.css";
import "trix/dist/trix";
import "trix/dist/trix.css";
import { PresentationFieldProps, usePresentationContext } from "src/components/PresentationContext";

export interface RichTextFieldProps {
export interface RichTextFieldProps extends Pick<PresentationFieldProps, "fullWidth"> {
/** The initial html value to show in the trix editor. */
value: string | undefined;
onChange: (html: string | undefined, text: string | undefined, mergeTags: string[]) => void;
Expand All @@ -28,8 +29,6 @@ export interface RichTextFieldProps {
onFocus?: () => void;
/** For rendering formatted text */
readOnly?: boolean;
/** Will set width to: 100% */
fullWidth?: boolean;
}

/**
Expand All @@ -40,7 +39,17 @@ export interface RichTextFieldProps {
* We also integrate [tributejs]{@link https://github.com/zurb/tribute} for @ mentions.
*/
export function RichTextField(props: RichTextFieldProps) {
const { mergeTags, label, value = "", onChange, onBlur = noop, onFocus = noop, readOnly, fullWidth } = props;
const { fieldProps } = usePresentationContext();
const {
mergeTags,
label,
value = "",
onChange,
onBlur = noop,
onFocus = noop,
readOnly,
fullWidth = fieldProps?.fullWidth ?? false,
} = props;

// We get a reference to the Editor instance after trix-init fires
const [editor, setEditor] = useState<Editor>();
Expand Down
2 changes: 2 additions & 0 deletions src/inputs/SelectField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ function Template(args: SelectFieldProps<any, any>) {
disabledOptions={[options[0].id, { value: options[3].id, reason: "Example disabled tooltip" }]}
helperText="Disabled options can optionally have tooltip text"
/>

<TestSelectField {...args} fullWidth label="Full Width" value={options[2].id} options={options} />
</div>

<div css={Css.df.fdc.gap2.$}>
Expand Down
3 changes: 3 additions & 0 deletions src/inputs/TextAreaField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export function TextAreaStyles() {
<TextField label="Regular Field For Reference" value="value" onChange={() => {}} />
</FormLines>

<h1 css={Css.lg.$}>Full Width</h1>
<TestTextArea label="Description" value="An example description text." fullWidth />

<h1 css={Css.lg.$}>Modified for Blueprint To Do Title</h1>
<TestTextArea
label="Title"
Expand Down
5 changes: 5 additions & 0 deletions src/inputs/TextField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ export function TextFieldStyles() {
endAdornment={<Icon icon="star" />}
/>
</div>

<div css={Css.df.fdc.gap2.$}>
<h1 css={Css.lg.$}>Full Width</h1>
<TestTextField fullWidth value="" label="Name" />
</div>
</div>
);
}
Expand Down
Loading
Loading