Skip to content

Commit

Permalink
feat: introduce NumberField component
Browse files Browse the repository at this point in the history
  • Loading branch information
mfal committed Jan 31, 2024
1 parent 6f41d38 commit 33b451d
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 20 deletions.
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"./Link": "./dist/Link.js",
"./Navigation": "./dist/Navigation.js",
"./Note": "./dist/Note.js",
"./NumberField": "./dist/NumberField.js",
"./RadioGroup": "./dist/RadioGroup.js",
"./StatusIcon": "./dist/StatusIcon.js",
"./Switch": "./dist/Switch.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding-block: var(--button--padding-squished-y);
padding-inline: var(--button--padding-squished-x);
font-size: var(--font-size--default);
Expand Down
113 changes: 113 additions & 0 deletions packages/components/src/components/NumberField/NumberField.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
@use "@/styles/mixins/textInput.scss";

.group {
order: 2;
display: grid;
grid-template-columns: 1fr max-content;
grid-template-areas:
"input increment"
"input decrement";
}

.input {
@include textInput.textInput();
grid-area: input;
}

.decrementButton {
grid-area: decrement;
}

.incrementButton {
grid-area: increment;
}

.group.group {
.decrementButton,
.incrementButton {
color: var(--form-field-control--color);
border-width: var(--form-control--border-width);
border-color: var(--form-field-control--border-color);
border-style: solid;
}
}

@media (pointer: fine) {
.input {
border-start-end-radius: 0;
border-end-end-radius: 0;
}

.incrementButton {
[data-icon="plus"] {
display: none;
}
}

.decrementButton {
[data-icon="minus"] {
display: none;
}
}

.group.group {
.decrementButton,
.incrementButton {
border-inline-start: none;
border-start-start-radius: 0;
border-end-start-radius: 0;
padding-block: 0;
}

.decrementButton {
border-start-end-radius: 0;
}

.incrementButton {
border-block-end: none;
border-end-end-radius: 0;
}
}
}

@media (pointer: coarse) {
.group {
grid-template-columns: auto 1fr auto;
grid-template-areas: "decrement input increment";
}

.input {
border-radius: 0;
}

.decrementButton {
[data-icon="chevron-down"] {
display: none;
}
}

.incrementButton {
[data-icon="chevron-up"] {
display: none;
}
}

.group.group {
.decrementButton,
.incrementButton {
padding-inline: var(--form-control--padding-x);
}

.decrementButton {
border-start-end-radius: 0;
border-end-end-radius: 0;
border-inline-end: none;
}

.incrementButton {
border-start-start-radius: 0;
border-end-start-radius: 0;
border-inline-start: none;
}
}
}
67 changes: 67 additions & 0 deletions packages/components/src/components/NumberField/NumberField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { FC, PropsWithChildren } from "react";
import * as Aria from "react-aria-components";
import formFieldStyles from "../FormField/FormField.module.scss";
import styles from "./NumberField.module.scss";
import clsx from "clsx";
import { PropsContext, PropsContextProvider } from "@/lib/propsContext";
import { FieldError } from "@/components/FieldError";
import { Button } from "@/components/Button";
import { Icon } from "@/components/Icon";
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";

export interface NumberFieldProps
extends PropsWithChildren<Omit<Aria.NumberFieldProps, "children">> {}

export const NumberField: FC<NumberFieldProps> = (props) => {
const { children, className, ...rest } = props;

const rootClassName = clsx(formFieldStyles.formField, className);

const propsContext: PropsContext = {
Label: {
className: formFieldStyles.label,
optional: !props.isRequired,
},
FieldDescription: {
className: formFieldStyles.fieldDescription,
},
FieldError: {
className: formFieldStyles.customFieldError,
},
};

return (
<Aria.NumberField {...rest} className={rootClassName}>
<Aria.Group className={styles.group}>
<Button
slot="decrement"
className={styles.decrementButton}
small
variant="plain"
>
<Icon faIcon={faChevronDown} />
<Icon faIcon={faMinus} />
</Button>
<Aria.Input className={styles.input} />
<Button
slot="increment"
className={styles.incrementButton}
small
variant="plain"
>
<Icon faIcon={faChevronUp} />
<Icon faIcon={faPlus} />
</Button>
</Aria.Group>
<PropsContextProvider props={propsContext}>
{children}
</PropsContextProvider>
<FieldError className={styles.fieldError} />
</Aria.NumberField>
);
};

export default NumberField;
3 changes: 3 additions & 0 deletions packages/components/src/components/NumberField/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { NumberField } from "./NumberField";
export { type NumberFieldProps, NumberField } from "./NumberField";
export default NumberField;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Meta, StoryObj } from "@storybook/react";
import { NumberField } from "../index";
import React from "react";
import { Label } from "@/components/Label";
import { action } from "@storybook/addon-actions";
import FieldDescription from "@/components/FieldDescription/FieldDescription";
import { FieldError } from "@/components/FieldError";

const meta: Meta<typeof NumberField> = {
title: "Forms/NumberField",
component: NumberField,
render: (props) => (
<NumberField onChange={action("onChange")} {...props}>
<Label>Age</Label>
</NumberField>
),
};

export default meta;

type Story = StoryObj<typeof NumberField>;

export const Default: Story = {};

export const Disabled: Story = { args: { isDisabled: true } };

export const Required: Story = {
args: { isRequired: true },
};

export const WithFieldDescription: Story = {
render: (props) => (
<NumberField {...props} minValue={5} maxValue={10}>
<Label>Age</Label>
<FieldDescription>Enter your age</FieldDescription>
</NumberField>
),
};

export const WithDefaultValue: Story = {
render: (props) => (
<NumberField {...props} defaultValue={34}>
<Label>Age</Label>
</NumberField>
),
};

export const WithFieldError: Story = {
render: (props) => (
<NumberField {...props} isInvalid isRequired>
<Label>Age</Label>
<FieldError>Age is required</FieldError>
</NumberField>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Meta, StoryObj } from "@storybook/react";
import { NumberField } from "../index";
import React from "react";
import { Label } from "@/components/Label";
import { action } from "@storybook/addon-actions";

const meta: Meta<typeof NumberField> = {
title: "Forms/NumberField/EdgeCases",
component: NumberField,
render: (props) => (
<NumberField onChange={action("onChange")} {...props}>
<Label>Age</Label>
</NumberField>
),
};

export default meta;

type Story = StoryObj<typeof NumberField>;

export const WithDisabledDecrement: Story = {
args: { minValue: 5, defaultValue: 5 },
};

export const WithDisabledIncrement: Story = {
args: { maxValue: 5, defaultValue: 5 },
};
18 changes: 18 additions & 0 deletions packages/components/src/styles/mixins/formControl.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@mixin formControl {
color: var(--form-field-control--color);

border-width: var(--form-control--border-width);
border-style: var(--form-control--border-style);
border-radius: var(--form-control--border-radius);
border-color: var(--form-field-control--border-color);

padding-block: var(--form-control--padding-y);
padding-inline: var(--form-control--padding-x);

background-color: var(--form-field-control--background-color);

&:focus-visible,
&:focus {
outline: none;
}
}
24 changes: 4 additions & 20 deletions packages/components/src/styles/mixins/textInput.scss
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
@mixin textInput {
& {
order: 2;

color: var(--form-field-control--color);

border-width: var(--form-control--border-width);
border-style: var(--form-control--border-style);
border-radius: var(--form-control--border-radius);
border-color: var(--form-field-control--border-color);
@use "./formControl";

padding-block: var(--form-control--padding-y);
padding-inline: var(--form-control--padding-x);

background-color: var(--form-field-control--background-color);
}

&:focus-visible,
&:focus {
outline: none;
}
@mixin textInput {
order: 2;
@include formControl.formControl();
}
1 change: 1 addition & 0 deletions packages/components/vite.build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default defineConfig(
Link: "./src/components/Link/index.ts",
Navigation: "./src/components/Navigation/index.ts",
Note: "./src/components/Note/index.ts",
NumberField: "./src/components/NumberField/index.ts",
Radio: "./src/components/RadioGroup/components/Radio/index.ts",
RadioGroup: "./src/components/RadioGroup/index.ts",
StatusIcon: "./src/components/StatusIcon/index.ts",
Expand Down

0 comments on commit 33b451d

Please sign in to comment.