Skip to content

Commit

Permalink
feat: add text input along with label, hint-text, error-text and form…
Browse files Browse the repository at this point in the history
…-group components. (#212)
  • Loading branch information
hamza14khan authored Sep 16, 2024
1 parent 5ac3adb commit 4ce451f
Show file tree
Hide file tree
Showing 16 changed files with 582 additions and 8 deletions.
15 changes: 13 additions & 2 deletions apps/docs/content/3-components/2-library/21-label.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ status: in-development

<ComponentStatusBlock componentId="label" />

## When to use this component
The `Label` component is used to associate a text label with a form input element, helping improve accessibility and user experience. The `size` prop allows you to adjust the label size based on the form's design requirements.

## When not to use this component
## When to Use This Component

- **Form Labels**: Use this component whenever you need to label form elements like text inputs, dropdowns, checkboxes, or radio buttons to improve accessibility and usability.
- **Responsive Text**: Use the `size` prop to control the text size, making it larger or smaller based on the context of the form or layout.

- **Accessibility**: The `htmlFor` prop ensures the label is properly associated with the corresponding input element, which improves accessibility for users who rely on screen readers.

## When Not to Use This Component

- **Standalone Text**: If you need a simple text element that’s not associated with a form control, use a standard text component rather than `Label`.

- **Non-Label Content**: If the element you are wrapping isn’t directly connected to an input (e.g., a non-interactive description), consider using a `span`, `p`, or another semantic element instead of `Label`.
19 changes: 17 additions & 2 deletions apps/docs/content/3-components/2-library/40-text-input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ status: in-development

<ComponentStatusBlock componentId="text-input" />

## When to use this component
The `TextInput` component is used to allow users to enter single-line text, with options to adjust the width dynamically using `halfFluid`, `fullFluid`, or `characterWidth`. It can be used with prefixes, suffixes, and error states for flexible form input scenarios.

## When not to use this component
## When to Use This Component

- **Single-Line Text Inputs**: Use this component for short, single-line inputs such as names, email addresses, phone numbers, and short codes (e.g., postal codes).

- **Customizable Widths**: Utilise the `halfFluid` or `fullFluid` properties for inputs that need to fit in responsive layouts. The `characterWidth` prop allows you to specify a custom width based on the number of characters the input should accommodate.

- **Error States**: For forms where invalid input needs to be flagged, the `hasError` prop visually indicates errors with a red border.

- **Custom Prefixes or Suffixes**: This component supports adding contextual information next to the input (e.g., units, symbols) using `prefix` and `suffix` props.

## When Not to Use This Component

- **Multi-Line Text Fields**: If your input requires multiple lines (such as a message box), use a `textarea` instead of `TextInput`.
- **Input with Formatting Requirements**: If input masking or format validation (like date pickers or rich text editors) is necessary, consider using a specialised component.

- **Non-Textual Inputs**: This component is not suitable for non-textual input types like checkboxes, radio buttons, or dropdown menus.
5 changes: 4 additions & 1 deletion apps/docs/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ analyze
webpages
NonVisual
customizable
Customizable
Color
color
colors
Expand All @@ -62,4 +63,6 @@ YAML
Tooltip
centered
data-testid
flexbox-based
flexbox-based
checkboxes
dropdown
8 changes: 5 additions & 3 deletions apps/docs/src/lib/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,9 @@ export function getComponents(): ComponentDetail[] {
{
platform: {
id: 'react',
href: '?path=/docs/form-textinput--docs',
},
status: 'considering',
status: 'alpha',
},
],
},
Expand Down Expand Up @@ -467,7 +468,7 @@ export function getComponents(): ComponentDetail[] {
],
},
{
id: 'Label',
id: 'label',
name: 'Label',
statuses: [
{
Expand All @@ -486,8 +487,9 @@ export function getComponents(): ComponentDetail[] {
{
platform: {
id: 'react',
href: '?path=docs/typography-label--docs',
},
status: 'considering',
status: 'alpha',
},
],
},
Expand Down
9 changes: 9 additions & 0 deletions examples/vite/src/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
Container,
IconButton,
PhaseBanner,
TextInput,
FormGroup,
Label,
HintText,
} from "@govie-react/ds";

export function App() {
Expand All @@ -23,6 +27,11 @@ export function App() {
/>
<Container>
<Heading>Heading</Heading>
<FormGroup>
<Label htmlFor="text-input">4 characters width</Label>
<HintText>Hint Text</HintText>
<TextInput characterWidth={40} id="text-input" />
</FormGroup>
<PhaseBanner level="alpha">This is a pre-release version</PhaseBanner>
<Link
href="https://www.google.com"
Expand Down
44 changes: 44 additions & 0 deletions packages/react/ds/src/error-text/error-text.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ErrorSize, ErrorText } from './error-text.js';

const meta = {
title: 'Form/ErrorText',
parameters: {
docs: {
description: {
component:
'Use hint text alongside a form input for help that’s relevant to the majority of users, like how their information will be used, or where to find it.',
},
},
},
component: ErrorText,
} satisfies Meta<typeof ErrorText>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
argTypes: {
size: {
control: 'radio',
options: Object.values(ErrorSize),
table: {
category: 'Appearance',
type: { summary: 'Size of label' },
defaultValue: { summary: ErrorSize.md },
},
},
children: {
control: 'text',
table: {
category: 'Content',
type: { summary: 'React.ReactNode' },
defaultValue: { summary: 'Error' },
},
},
},
args: {
children: 'Error',
size: ErrorSize.md,
},
};
30 changes: 30 additions & 0 deletions packages/react/ds/src/error-text/error-text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { Text } from '../text/text.js';

export enum ErrorSize {
sm = 'sm',
md = 'md',
lg = 'lg',
}

// Extend `React.HTMLAttributes<HTMLParagraphElement>` so that
// the component can accept all the standard attributes and events that a `<p>` element can handle.
export type ErrorTextProps = React.HTMLAttributes<HTMLParagraphElement> & {
size?: ErrorSize;
};

export const ErrorText: React.FC<ErrorTextProps> = ({
size = ErrorSize.md,
...props
}) => {
return (
<Text
as="p"
size={size}
className="gi-font-bold gi-leading-5 gi-text-red-600 gi-clear-both gi-block !gi-mb-[14px] !gi-mt-0"
{...props}
>
{props.children}
</Text>
);
};
23 changes: 23 additions & 0 deletions packages/react/ds/src/form-group/form-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

export type FormGroupProps = React.HTMLAttributes<HTMLDivElement> & {
hasError?: boolean;
children: React.ReactNode;
};

export const FormGroup = React.forwardRef<HTMLDivElement, FormGroupProps>(
({ hasError = false, children, ...props }, ref) => {
return (
<div
ref={ref}
className={`gi-pt-2 gi-mb-4 ${hasError ? 'gi-px-4 gi-border-solid gi-border-l-xl gi-border-red-600' : ''}`}
{...props}
>
{children}
</div>
);
},
);

// Set the displayName for debugging purposes
FormGroup.displayName = 'FormGroup';
44 changes: 44 additions & 0 deletions packages/react/ds/src/hint-text/hint-text.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Meta, StoryObj } from '@storybook/react';
import { HintSize, HintText } from './hint-text.js';

const meta = {
title: 'Form/HintText',
parameters: {
docs: {
description: {
component:
'Use hint text alongside a form input for help that’s relevant to the majority of users, like how their information will be used, or where to find it.',
},
},
},
component: HintText,
} satisfies Meta<typeof HintText>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
argTypes: {
size: {
control: 'radio',
options: Object.values(HintSize),
table: {
category: 'Appearance',
type: { summary: 'Size of label' },
defaultValue: { summary: HintSize.md },
},
},
children: {
control: 'text',
table: {
category: 'Content',
type: { summary: 'React.ReactNode' },
defaultValue: { summary: 'Hint' },
},
},
},
args: {
children: 'Hint',
size: HintSize.md,
},
};
30 changes: 30 additions & 0 deletions packages/react/ds/src/hint-text/hint-text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { Text } from '../text/text.js';

export enum HintSize {
sm = 'sm',
md = 'md',
lg = 'lg',
}

// Extend `React.InputHTMLAttributes<HTMLInputElement>` so that
// the component can accept all the standard attributes and events that an `<input>` element can handle.
export type HintTextProps = React.HTMLAttributes<HTMLInputElement> & {
size?: HintSize;
};

// Use React.forwardRef to support refs properly
export const HintText: React.FC<HintTextProps> = ({ size, ...props }, ref) => {
return (
<Text
size={size}
className="gi-font-normal gi-leading-5 gi-text-gray-600 !gi-mb-[10px]"
{...props}
>
{props.children}
</Text>
);
};

// Set the displayName for debugging purposes
HintText.displayName = 'HintText';
5 changes: 5 additions & 0 deletions packages/react/ds/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ export * from './link/link.js';
export * from './paragraph/paragraph.js';
export * from './phase-banner/phase-banner.js';
export * from './text/text.js';
export * from './text-input/text-input.js';
export * from './label/label.js';
export * from './hint-text/hint-text.js';
export * from './form-group/form-group.js';
export * from './error-text/error-text.js';
60 changes: 60 additions & 0 deletions packages/react/ds/src/label/label.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Label, LabelSize } from './label.js';

const meta = {
title: 'Typography/Label',
parameters: {
docs: {
description: {
component: 'Label element to wrap label-text and a form input.',
},
},
},
component: Label,
} satisfies Meta<typeof Label>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
argTypes: {
ref: {
control: false,
table: {
category: 'Ref',
type: { summary: 'React.Ref<HTMLLabelElement>' },
defaultValue: { summary: '-' },
},
},
size: {
control: 'radio',
options: Object.values(LabelSize),
table: {
category: 'Appearance',
type: { summary: 'Size of label' },
defaultValue: { summary: LabelSize.md },
},
},
htmlFor: {
control: 'text',
table: {
category: 'Accessibility',
type: { summary: 'string' },
defaultValue: { summary: '-' },
},
},
children: {
control: 'text',
table: {
category: 'Content',
type: { summary: 'React.ReactNode' },
defaultValue: { summary: 'Label' },
},
},
},
args: {
htmlFor: 'input-id',
size: LabelSize.md,
children: 'Label',
},
};
31 changes: 31 additions & 0 deletions packages/react/ds/src/label/label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';

export enum LabelSize {
sm = 'sm',
md = 'md',
lg = 'lg',
}

// Extend `React.LabelHTMLAttributes<HTMLLabelElement>` for correct label attributes
export type LabelProps = React.LabelHTMLAttributes<HTMLLabelElement> & {
size?: LabelSize;
};

// Use React.forwardRef to support refs properly
export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
({ size = LabelSize.md, htmlFor, ...props }, ref) => {
return (
<label
className={`gi-text-${size} gi-leading-5 gi-text-gray-950 gi-mb-2 gi-block`}
ref={ref}
htmlFor={htmlFor}
{...props}
>
{props.children}
</label>
);
},
);

// Set the displayName for debugging purposes
Label.displayName = 'Label';
Loading

0 comments on commit 4ce451f

Please sign in to comment.