Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into feat/breadcrumb
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas.J.Han <[email protected]>
  • Loading branch information
lukasjhan committed Aug 20, 2024
2 parents 246d114 + 5f68745 commit 3631c5c
Show file tree
Hide file tree
Showing 5 changed files with 478 additions and 1 deletion.
79 changes: 79 additions & 0 deletions packages/core/lib/components/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { forwardRef, useState } from 'react';
import { Label } from './Label';

type TextAreaProps = {
id: string;
title?: string;
description?: string;
size?: 'small' | 'medium' | 'large';
maxLength?: number;
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;

export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
(
{
title,
description,
id,
placeholder,
size = 'medium',
maxLength,
onChange,
...props
},
ref
) => {
const [charCount, setCharCount] = useState(0);

const sizeClasses = {
small: 'h-24',
medium: 'h-32',
large: 'h-40',
}[size];

const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setCharCount(e.target.value.length);
if (onChange) {
onChange(e);
}
};

return (
<div className="flex flex-col gap-1 justify-center">
{title && (
<Label htmlFor={id} weight="bold">
{title}
</Label>
)}
{description && (
<Label size={'s'} color={'gray-50'}>
{description}
</Label>
)}
<div className="relative">
<textarea
ref={ref}
id={id}
className={`
w-full ${sizeClasses} px-4 py-3 text-gray-70 border rounded-4
focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary mt-3
border-gray-50 transition duration-150 ease-in-out resize-none
`}
placeholder={placeholder}
maxLength={maxLength}
onChange={handleChange}
{...props}
/>
{maxLength && (
<div className="flex justify-end gap-1">
<Label color={'primary'} size={'xs'}>
{charCount}
</Label>
<Label size={'xs'}>{`/${maxLength}`}</Label>
</div>
)}
</div>
</div>
);
}
);
85 changes: 85 additions & 0 deletions packages/core/lib/components/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { forwardRef } from 'react';
import { Label } from './Label';

type TextInputProps = {
id: string;
title?: string;
description?: string;
helpText?: string;
error?: string;
length?: 'x-short' | 'short' | 'middle' | 'long' | 'full';
} & React.InputHTMLAttributes<HTMLInputElement>;

export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
(
{
title,
description,
helpText,
error,
id,
placeholder,
length = 'middle',
...props
},
ref
) => {
const inputId = id;
const helperTextId = `${inputId}-help`;
const errorId = `${inputId}-error`;

const lengthClasses = {
'x-short': 'w-16',
short: 'w-32',
middle: 'w-64',
long: 'w-128',
full: 'w-full',
}[length];

return (
<div className="flex flex-col gap-1 justify-center">
{title && (
<Label htmlFor={inputId} weight="bold">
{title}
</Label>
)}
{description && (
<Label size={'s'} color={'gray-50'}>
{description}
</Label>
)}
<div className="relative">
<input
ref={ref}
id={inputId}
type="text"
className={`
${lengthClasses} px-4 py-3 text-gray-70 border rounded-2 focus:border-primary
focus:outline-none focus:ring-1 focus:ring-primary mt-3
${error ? 'border-danger' : 'border-gray-50'}
transition duration-150 ease-in-out
`}
placeholder={placeholder}
aria-describedby={error ? errorId : helperTextId}
aria-invalid={error ? 'true' : 'false'}
{...props}
/>
</div>
{error ? (
<Label id={errorId} size={'s'} color={'danger'} className="mt-1">
{error}
</Label>
) : helpText ? (
<Label
id={helperTextId}
size={'s'}
color={'gray-50'}
className="mt-1"
>
{helpText}
</Label>
) : null}
</div>
);
}
);
13 changes: 12 additions & 1 deletion packages/core/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ import { LinkButton } from './components/LinkButton';
import { Tag } from './components/Tag';
import { Spinner } from './components/Spinner';
import { Badge } from './components/Badge';
import { TextInput } from './components/TextInput';
import { TextArea } from './components/TextArea';
import { Breadcrumb } from './components/Breadcrumb';

export { Display, Heading, Title, Body, Detail, Label, Link, colors };
export { Button, LinkButton, Tag, Spinner, Badge, Breadcrumb };
export {
Button,
LinkButton,
Tag,
Spinner,
Badge,
TextInput,
TextArea,
Breadcrumb,
};
123 changes: 123 additions & 0 deletions stories/core/TextArea.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { TextArea } from '../../packages/core/lib';

const meta = {
title: 'Components/TextArea',
component: TextArea,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
size: {
options: ['small', 'medium', 'large'],
control: {
type: 'select',
},
},
onChange: { action: 'changed' },
},
} satisfies Meta<typeof TextArea>;

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

export const Default: Story = {
args: {
id: 'text-area-id',
title: 'Title',
description: 'Description',
placeholder: 'Placeholder',
size: 'medium',
maxLength: 100,
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
console.log(e.target.value),
},
};

export const NoMaxLength: Story = {
args: {
id: 'text-area-id-3',
title: 'Title',
description: 'Description',
placeholder: 'Placeholder',
size: 'medium',
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
console.log(e.target.value),
},
};

export const Small: Story = {
args: {
id: 'text-area-id-2',
title: 'Title',
description: 'Description',
placeholder: 'Placeholder',
size: 'small',
maxLength: 100,
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
console.log(e.target.value),
},
};

export const Medium: Story = {
args: {
id: 'text-area-id-4',
title: 'Title',
description: 'Description',
placeholder: 'Placeholder',
size: 'medium',
maxLength: 100,
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
console.log(e.target.value),
},
};

export const Large: Story = {
args: {
id: 'text-area-id-5',
title: 'Title',
description: 'Description',
placeholder: 'Placeholder',
size: 'large',
maxLength: 100,
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
console.log(e.target.value),
},
};

export const NoTitle: Story = {
args: {
id: 'text-area-id-6',
description: 'Description',
placeholder: 'Placeholder',
size: 'medium',
maxLength: 100,
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
console.log(e.target.value),
},
};

export const NoDescription: Story = {
args: {
id: 'text-area-id-7',
placeholder: 'Placeholder',
size: 'medium',
maxLength: 100,
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
console.log(e.target.value),
},
};

export const NoPlaceholder: Story = {
args: {
id: 'text-area-id-8',
title: 'Title',
description: 'Description',
size: 'medium',
maxLength: 100,
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
console.log(e.target.value),
},
};
Loading

0 comments on commit 3631c5c

Please sign in to comment.