-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce NumberField component
- Loading branch information
Showing
10 changed files
with
290 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
packages/components/src/components/NumberField/NumberField.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
67
packages/components/src/components/NumberField/NumberField.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
55 changes: 55 additions & 0 deletions
55
packages/components/src/components/NumberField/stories/Default.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
), | ||
}; |
27 changes: 27 additions & 0 deletions
27
packages/components/src/components/NumberField/stories/EdgeCases.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters