Merge remote-tracking branch 'origin/andrehadianto/design-system-components' into andrehadianto/T-4869-toast
This commit is contained in:
commit
267b52a352
@ -17,6 +17,7 @@
|
|||||||
"@types/react": "^18.2.42",
|
"@types/react": "^18.2.42",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^18.2.17",
|
||||||
"assert": "^2.1.0",
|
"assert": "^2.1.0",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
"date-fns": "^3.3.1",
|
"date-fns": "^3.3.1",
|
||||||
"downshift": "^8.2.3",
|
"downshift": "^8.2.3",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
|
@ -118,6 +118,29 @@ export const buttonTheme = tv(
|
|||||||
'disabled:border-transparent',
|
'disabled:border-transparent',
|
||||||
'disabled:shadow-none',
|
'disabled:shadow-none',
|
||||||
],
|
],
|
||||||
|
link: [
|
||||||
|
'p-0',
|
||||||
|
'gap-1.5',
|
||||||
|
'text-elements-link',
|
||||||
|
'rounded',
|
||||||
|
'focus-ring',
|
||||||
|
'hover:underline',
|
||||||
|
'hover:text-elements-link-hovered',
|
||||||
|
'disabled:text-controls-disabled',
|
||||||
|
'disabled:hover:no-underline',
|
||||||
|
],
|
||||||
|
'link-emphasized': [
|
||||||
|
'p-0',
|
||||||
|
'gap-1.5',
|
||||||
|
'text-elements-high-em',
|
||||||
|
'rounded',
|
||||||
|
'underline',
|
||||||
|
'focus-ring',
|
||||||
|
'hover:text-elements-link-hovered',
|
||||||
|
'disabled:text-controls-disabled',
|
||||||
|
'disabled:hover:no-underline',
|
||||||
|
'dark:text-elements-on-high-contrast',
|
||||||
|
],
|
||||||
unstyled: [],
|
unstyled: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||||
|
|
||||||
|
export const CrossIcon = (props: CustomIconProps) => {
|
||||||
|
return (
|
||||||
|
<CustomIcon
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5 5L19 19M19 5L5 19"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</CustomIcon>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||||
|
|
||||||
|
export const InfoSquareIcon = (props: CustomIconProps) => {
|
||||||
|
return (
|
||||||
|
<CustomIcon
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M5.96786 2.5C5.52858 2.49999 5.14962 2.49997 4.83748 2.52548C4.50802 2.55239 4.18034 2.61182 3.86503 2.77249C3.39462 3.01217 3.01217 3.39462 2.77249 3.86503C2.61182 4.18034 2.55239 4.50802 2.52548 4.83748C2.49997 5.14962 2.49999 5.52857 2.5 5.96785V14.0321C2.49999 14.4714 2.49997 14.8504 2.52548 15.1625C2.55239 15.492 2.61182 15.8197 2.77249 16.135C3.01217 16.6054 3.39462 16.9878 3.86503 17.2275C4.18034 17.3882 4.50802 17.4476 4.83748 17.4745C5.14962 17.5 5.52858 17.5 5.96786 17.5H14.0321C14.4714 17.5 14.8504 17.5 15.1625 17.4745C15.492 17.4476 15.8197 17.3882 16.135 17.2275C16.6054 16.9878 16.9878 16.6054 17.2275 16.135C17.3882 15.8197 17.4476 15.492 17.4745 15.1625C17.5 14.8504 17.5 14.4714 17.5 14.0321V5.96786C17.5 5.52858 17.5 5.14962 17.4745 4.83748C17.4476 4.50802 17.3882 4.18034 17.2275 3.86503C16.9878 3.39462 16.6054 3.01217 16.135 2.77249C15.8197 2.61182 15.492 2.55239 15.1625 2.52548C14.8504 2.49997 14.4714 2.49999 14.0322 2.5H5.96786ZM8.33333 9.16667C8.33333 8.70643 8.70643 8.33333 9.16667 8.33333H10C10.4602 8.33333 10.8333 8.70643 10.8333 9.16667V13.3333C10.8333 13.7936 10.4602 14.1667 10 14.1667C9.53976 14.1667 9.16667 13.7936 9.16667 13.3333V10C8.70643 10 8.33333 9.6269 8.33333 9.16667ZM10 5.83333C9.53976 5.83333 9.16667 6.20643 9.16667 6.66667C9.16667 7.1269 9.53976 7.5 10 7.5C10.4602 7.5 10.8333 7.1269 10.8333 6.66667C10.8333 6.20643 10.4602 5.83333 10 5.83333Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</CustomIcon>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||||
|
|
||||||
|
export const SearchIcon = (props: CustomIconProps) => {
|
||||||
|
return (
|
||||||
|
<CustomIcon
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 20L16.05 16.05M18 11C18 14.866 14.866 18 11 18C7.13401 18 4 14.866 4 11C4 7.13401 7.13401 4 11 4C14.866 4 18 7.13401 18 11Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</CustomIcon>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||||
|
|
||||||
|
export const WarningIcon = (props: CustomIconProps) => {
|
||||||
|
return (
|
||||||
|
<CustomIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10.4902 2.84406C11.1661 1.69 12.8343 1.69 13.5103 2.84406L22.0156 17.3654C22.699 18.5321 21.8576 19.9999 20.5056 19.9999H3.49483C2.14281 19.9999 1.30147 18.5321 1.98479 17.3654L10.4902 2.84406ZM12 9C12.4142 9 12.75 9.33579 12.75 9.75V13.25C12.75 13.6642 12.4142 14 12 14C11.5858 14 11.25 13.6642 11.25 13.25V9.75C11.25 9.33579 11.5858 9 12 9ZM13 15.75C13 16.3023 12.5523 16.75 12 16.75C11.4477 16.75 11 16.3023 11 15.75C11 15.1977 11.4477 14.75 12 14.75C12.5523 14.75 13 15.1977 13 15.75Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</CustomIcon>
|
||||||
|
);
|
||||||
|
};
|
@ -4,5 +4,9 @@ export * from './CheckIcon';
|
|||||||
export * from './ChevronGrabberHorizontal';
|
export * from './ChevronGrabberHorizontal';
|
||||||
export * from './ChevronLeft';
|
export * from './ChevronLeft';
|
||||||
export * from './ChevronRight';
|
export * from './ChevronRight';
|
||||||
|
export * from './InfoSquareIcon';
|
||||||
|
export * from './WarningIcon';
|
||||||
|
export * from './SearchIcon';
|
||||||
|
export * from './CrossIcon';
|
||||||
export * from './GlobeIcon';
|
export * from './GlobeIcon';
|
||||||
export * from './CheckRoundFilledIcon';
|
export * from './CheckRoundFilledIcon';
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import { VariantProps, tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const inlineNotificationTheme = tv({
|
||||||
|
slots: {
|
||||||
|
wrapper: ['rounded-xl', 'flex', 'gap-2', 'items-start', 'w-full', 'border'],
|
||||||
|
content: ['flex', 'flex-col', 'gap-1'],
|
||||||
|
title: [],
|
||||||
|
description: [],
|
||||||
|
icon: ['flex', 'items-start'],
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
info: {
|
||||||
|
wrapper: ['border-border-info-light', 'bg-base-bg-emphasized-info'],
|
||||||
|
title: ['text-elements-on-emphasized-info'],
|
||||||
|
description: ['text-elements-on-emphasized-info'],
|
||||||
|
icon: ['text-elements-info'],
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
wrapper: ['border-border-danger-light', 'bg-base-bg-emphasized-danger'],
|
||||||
|
title: ['text-elements-on-emphasized-danger'],
|
||||||
|
description: ['text-elements-on-emphasized-danger'],
|
||||||
|
icon: ['text-elements-danger'],
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
wrapper: [
|
||||||
|
'border-border-warning-light',
|
||||||
|
'bg-base-bg-emphasized-warning',
|
||||||
|
],
|
||||||
|
title: ['text-elements-on-emphasized-warning'],
|
||||||
|
description: ['text-elements-on-emphasized-warning'],
|
||||||
|
icon: ['text-elements-warning'],
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
wrapper: [
|
||||||
|
'border-border-success-light',
|
||||||
|
'bg-base-bg-emphasized-success',
|
||||||
|
],
|
||||||
|
title: ['text-elements-on-emphasized-success'],
|
||||||
|
description: ['text-elements-on-emphasized-success'],
|
||||||
|
icon: ['text-elements-success'],
|
||||||
|
},
|
||||||
|
generic: {
|
||||||
|
wrapper: ['border-border-separator', 'bg-base-bg-emphasized'],
|
||||||
|
title: ['text-elements-high-em'],
|
||||||
|
description: ['text-elements-on-emphasized-info'],
|
||||||
|
icon: ['text-elements-high-em'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
sm: {
|
||||||
|
wrapper: ['px-2', 'py-2'],
|
||||||
|
title: ['leading-4', 'text-xs'],
|
||||||
|
description: ['leading-4', 'text-xs'],
|
||||||
|
icon: ['h-4', 'w-4'],
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
wrapper: ['px-3', 'py-3'],
|
||||||
|
title: ['leading-5', 'tracking-[-0.006em]', 'text-sm'],
|
||||||
|
description: ['leading-5', 'tracking-[-0.006em]', 'text-sm'],
|
||||||
|
icon: ['h-5', 'w-5'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hasDescription: {
|
||||||
|
true: {
|
||||||
|
title: ['font-medium'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'generic',
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InlineNotificationTheme = VariantProps<
|
||||||
|
typeof inlineNotificationTheme
|
||||||
|
>;
|
@ -0,0 +1,68 @@
|
|||||||
|
import React, { ReactNode, useCallback } from 'react';
|
||||||
|
import { ComponentPropsWithoutRef } from 'react';
|
||||||
|
import {
|
||||||
|
InlineNotificationTheme,
|
||||||
|
inlineNotificationTheme,
|
||||||
|
} from './InlineNotification.theme';
|
||||||
|
import { InfoSquareIcon } from 'components/shared/CustomIcon';
|
||||||
|
import { cloneIcon } from 'utils/cloneIcon';
|
||||||
|
|
||||||
|
export interface InlineNotificationProps
|
||||||
|
extends ComponentPropsWithoutRef<'div'>,
|
||||||
|
InlineNotificationTheme {
|
||||||
|
/**
|
||||||
|
* The title of the notification
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* The description of the notification
|
||||||
|
*/
|
||||||
|
description?: string;
|
||||||
|
/**
|
||||||
|
* The icon to display in the notification
|
||||||
|
* @default <InfoSquareIcon />
|
||||||
|
*/
|
||||||
|
icon?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A notification that is displayed inline with the content
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <InlineNotification title="Notification title goes here" />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const InlineNotification = ({
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
size,
|
||||||
|
variant,
|
||||||
|
icon,
|
||||||
|
...props
|
||||||
|
}: InlineNotificationProps) => {
|
||||||
|
const {
|
||||||
|
wrapper,
|
||||||
|
content,
|
||||||
|
title: titleClass,
|
||||||
|
description: descriptionClass,
|
||||||
|
icon: iconClass,
|
||||||
|
} = inlineNotificationTheme({ size, variant, hasDescription: !!description });
|
||||||
|
|
||||||
|
// Render custom icon or default icon
|
||||||
|
const renderIcon = useCallback(() => {
|
||||||
|
if (!icon) return <InfoSquareIcon className={iconClass()} />;
|
||||||
|
return cloneIcon(icon, { className: iconClass() });
|
||||||
|
}, [icon]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props} className={wrapper({ className })}>
|
||||||
|
{renderIcon()}
|
||||||
|
<div className={content()}>
|
||||||
|
<p className={titleClass()}>{title}</p>
|
||||||
|
{description && <p className={descriptionClass()}>{description}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './InlineNotification';
|
84
packages/frontend/src/components/shared/Input/Input.theme.ts
Normal file
84
packages/frontend/src/components/shared/Input/Input.theme.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { VariantProps, tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const inputTheme = tv(
|
||||||
|
{
|
||||||
|
slots: {
|
||||||
|
container: [
|
||||||
|
'flex',
|
||||||
|
'items-center',
|
||||||
|
'rounded-lg',
|
||||||
|
'relative',
|
||||||
|
'placeholder:text-elements-disabled',
|
||||||
|
'disabled:cursor-not-allowed',
|
||||||
|
'disabled:bg-controls-disabled',
|
||||||
|
],
|
||||||
|
label: ['text-sm', 'text-elements-high-em'],
|
||||||
|
description: ['text-xs', 'text-elements-low-em'],
|
||||||
|
input: [
|
||||||
|
'focus-ring',
|
||||||
|
'block',
|
||||||
|
'w-full',
|
||||||
|
'h-full',
|
||||||
|
'rounded-lg',
|
||||||
|
'text-elements-mid-em',
|
||||||
|
'shadow-sm',
|
||||||
|
'border',
|
||||||
|
'border-border-interactive',
|
||||||
|
'disabled:shadow-none',
|
||||||
|
'disabled:border-none',
|
||||||
|
],
|
||||||
|
icon: ['text-elements-mid-em'],
|
||||||
|
iconContainer: [
|
||||||
|
'absolute',
|
||||||
|
'inset-y-0',
|
||||||
|
'flex',
|
||||||
|
'items-center',
|
||||||
|
'z-10',
|
||||||
|
'cursor-pointer',
|
||||||
|
],
|
||||||
|
helperIcon: [],
|
||||||
|
helperText: ['flex', 'gap-2', 'items-center', 'text-elements-danger'],
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
state: {
|
||||||
|
default: {
|
||||||
|
input: '',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
input: [
|
||||||
|
'outline',
|
||||||
|
'outline-offset-0',
|
||||||
|
'outline-border-danger',
|
||||||
|
'shadow-none',
|
||||||
|
],
|
||||||
|
helperText: 'text-elements-danger',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
md: {
|
||||||
|
container: 'h-11',
|
||||||
|
input: ['text-sm pl-4 pr-4'],
|
||||||
|
icon: ['h-[18px] w-[18px]'],
|
||||||
|
helperText: 'text-sm',
|
||||||
|
helperIcon: ['h-5 w-5'],
|
||||||
|
},
|
||||||
|
sm: {
|
||||||
|
container: 'h-8',
|
||||||
|
input: ['text-xs pl-3 pr-3'],
|
||||||
|
icon: ['h-4 w-4'],
|
||||||
|
helperText: 'text-xs',
|
||||||
|
helperIcon: ['h-4 w-4'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'md',
|
||||||
|
state: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
responsiveVariants: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export type InputTheme = VariantProps<typeof inputTheme>;
|
100
packages/frontend/src/components/shared/Input/Input.tsx
Normal file
100
packages/frontend/src/components/shared/Input/Input.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import React, { ReactNode, useMemo } from 'react';
|
||||||
|
import { ComponentPropsWithoutRef } from 'react';
|
||||||
|
import { InputTheme, inputTheme } from './Input.theme';
|
||||||
|
import { WarningIcon } from 'components/shared/CustomIcon';
|
||||||
|
import { cloneIcon } from 'utils/cloneIcon';
|
||||||
|
import { cn } from 'utils/classnames';
|
||||||
|
|
||||||
|
export interface InputProps
|
||||||
|
extends InputTheme,
|
||||||
|
Omit<ComponentPropsWithoutRef<'input'>, 'size'> {
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
leftIcon?: ReactNode;
|
||||||
|
rightIcon?: ReactNode;
|
||||||
|
helperText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Input = ({
|
||||||
|
className,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
leftIcon,
|
||||||
|
rightIcon,
|
||||||
|
helperText,
|
||||||
|
size,
|
||||||
|
state,
|
||||||
|
...props
|
||||||
|
}: InputProps) => {
|
||||||
|
const styleProps = (({ size = 'md', state }) => ({
|
||||||
|
size,
|
||||||
|
state,
|
||||||
|
}))({ size, state });
|
||||||
|
|
||||||
|
const {
|
||||||
|
container: containerCls,
|
||||||
|
label: labelCls,
|
||||||
|
description: descriptionCls,
|
||||||
|
input: inputCls,
|
||||||
|
icon: iconCls,
|
||||||
|
iconContainer: iconContainerCls,
|
||||||
|
helperText: helperTextCls,
|
||||||
|
helperIcon: helperIconCls,
|
||||||
|
} = inputTheme({ ...styleProps });
|
||||||
|
|
||||||
|
const renderLabels = useMemo(
|
||||||
|
() => (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className={labelCls()}>{label}</p>
|
||||||
|
<p className={descriptionCls()}>{description}</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[labelCls, descriptionCls, label, description],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderLeftIcon = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<div className={iconContainerCls({ class: 'left-0 pl-4' })}>
|
||||||
|
{cloneIcon(leftIcon, { className: iconCls(), ariaHidden: true })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [cloneIcon, iconCls, iconContainerCls, leftIcon]);
|
||||||
|
|
||||||
|
const renderRightIcon = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<div className={iconContainerCls({ class: 'pr-4 right-0' })}>
|
||||||
|
{cloneIcon(rightIcon, { className: iconCls(), ariaHidden: true })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [cloneIcon, iconCls, iconContainerCls, rightIcon]);
|
||||||
|
|
||||||
|
const renderHelperText = useMemo(
|
||||||
|
() => (
|
||||||
|
<div className={helperTextCls()}>
|
||||||
|
{state &&
|
||||||
|
cloneIcon(<WarningIcon className={helperIconCls()} />, {
|
||||||
|
ariaHidden: true,
|
||||||
|
})}
|
||||||
|
<p>{helperText}</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[cloneIcon, state, helperIconCls, helperText, helperTextCls],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{renderLabels}
|
||||||
|
<div className={containerCls({ class: className })}>
|
||||||
|
{leftIcon && renderLeftIcon}
|
||||||
|
<input
|
||||||
|
className={cn(inputCls({ class: 'w-80' }), {
|
||||||
|
'pl-10': leftIcon,
|
||||||
|
})}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
{rightIcon && renderRightIcon}
|
||||||
|
</div>
|
||||||
|
{renderHelperText}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
2
packages/frontend/src/components/shared/Input/index.ts
Normal file
2
packages/frontend/src/components/shared/Input/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './Input';
|
||||||
|
export * from './Input.theme';
|
@ -7,7 +7,11 @@ import {
|
|||||||
} from './renders/checkbox';
|
} from './renders/checkbox';
|
||||||
import { avatars, avatarsFallback } from './renders/avatar';
|
import { avatars, avatarsFallback } from './renders/avatar';
|
||||||
import { renderBadges } from './renders/badge';
|
import { renderBadges } from './renders/badge';
|
||||||
import { renderButtonIcons, renderButtons } from './renders/button';
|
import {
|
||||||
|
renderButtonIcons,
|
||||||
|
renderButtons,
|
||||||
|
renderLinks,
|
||||||
|
} from './renders/button';
|
||||||
import {
|
import {
|
||||||
renderTabWithBadges,
|
renderTabWithBadges,
|
||||||
renderTabs,
|
renderTabs,
|
||||||
@ -15,6 +19,11 @@ import {
|
|||||||
} from './renders/tabs';
|
} from './renders/tabs';
|
||||||
import { useToast } from 'components/shared/Toast/useToast';
|
import { useToast } from 'components/shared/Toast/useToast';
|
||||||
import { Button } from 'components/shared/Button';
|
import { Button } from 'components/shared/Button';
|
||||||
|
import {
|
||||||
|
renderInlineNotificationWithDescriptions,
|
||||||
|
renderInlineNotifications,
|
||||||
|
} from './renders/inlineNotifications';
|
||||||
|
import { renderInputs } from './renders/input';
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -24,7 +33,7 @@ const Page = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full min-h-full">
|
<div className="relative h-full min-h-full">
|
||||||
<div className="flex flex-col items-center justify-center max-w-7xl mx-auto px-20 py-20">
|
<div className="flex flex-col items-center justify-center container mx-auto px-20 py-20">
|
||||||
<h1 className="text-4xl font-bold">Manual Storybook</h1>
|
<h1 className="text-4xl font-bold">Manual Storybook</h1>
|
||||||
<p className="mt-4 text-lg text-center text-gray-500">
|
<p className="mt-4 text-lg text-center text-gray-500">
|
||||||
Get started by editing{' '}
|
Get started by editing{' '}
|
||||||
@ -44,6 +53,11 @@ const Page = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* Button */}
|
{/* Button */}
|
||||||
<div className="flex flex-col gap-10 items-center justify-between">
|
<div className="flex flex-col gap-10 items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold">Input</h1>
|
||||||
|
<div className="flex w-full flex-col gap-10">{renderInputs()}</div>
|
||||||
|
|
||||||
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
|
||||||
<h1 className="text-2xl font-bold">Button</h1>
|
<h1 className="text-2xl font-bold">Button</h1>
|
||||||
<div className="flex flex-col gap-10">
|
<div className="flex flex-col gap-10">
|
||||||
{renderButtons()}
|
{renderButtons()}
|
||||||
@ -123,6 +137,29 @@ const Page = () => {
|
|||||||
<h1 className="text-2xl font-bold">Vertical Tabs</h1>
|
<h1 className="text-2xl font-bold">Vertical Tabs</h1>
|
||||||
{renderVerticalTabs()}
|
{renderVerticalTabs()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
|
||||||
|
{/* Inline notification */}
|
||||||
|
<div className="flex flex-col gap-10 items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold">Inline Notification</h1>
|
||||||
|
<div className="flex gap-1 flex-wrap">
|
||||||
|
{renderInlineNotifications()}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1 flex-wrap">
|
||||||
|
{renderInlineNotificationWithDescriptions()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
|
||||||
|
{/* Link */}
|
||||||
|
<div className="flex flex-col gap-10 items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold">Link</h1>
|
||||||
|
<div className="flex gap-4 items-center justify-center">
|
||||||
|
{renderLinks()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,3 +47,19 @@ export const renderButtonIcons = () => {
|
|||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const renderLinks = () => {
|
||||||
|
return ['link', 'link-emphasized', 'disabled'].map((variant) => (
|
||||||
|
<Button
|
||||||
|
variant={
|
||||||
|
variant !== 'disabled' ? (variant as ButtonTheme['variant']) : 'link'
|
||||||
|
}
|
||||||
|
key={variant}
|
||||||
|
leftIcon={<PlusIcon />}
|
||||||
|
rightIcon={<PlusIcon />}
|
||||||
|
disabled={variant === 'disabled'}
|
||||||
|
>
|
||||||
|
Link
|
||||||
|
</Button>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
import { InlineNotification } from 'components/shared/InlineNotification';
|
||||||
|
import { InlineNotificationTheme } from 'components/shared/InlineNotification/InlineNotification.theme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const inlineNotificationVariants = [
|
||||||
|
'info',
|
||||||
|
'danger',
|
||||||
|
'warning',
|
||||||
|
'success',
|
||||||
|
'generic',
|
||||||
|
];
|
||||||
|
const inlineNotificationSizes = ['md', 'sm'];
|
||||||
|
|
||||||
|
export const renderInlineNotifications = () => {
|
||||||
|
return inlineNotificationVariants.map((variant) => (
|
||||||
|
<div className="space-y-2" key={variant}>
|
||||||
|
{inlineNotificationSizes.map((size) => (
|
||||||
|
<InlineNotification
|
||||||
|
size={size as InlineNotificationTheme['size']}
|
||||||
|
variant={variant as InlineNotificationTheme['variant']}
|
||||||
|
key={`${variant}-${size}`}
|
||||||
|
title="Notification title goes here"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderInlineNotificationWithDescriptions = () => {
|
||||||
|
return inlineNotificationVariants.map((variant) => (
|
||||||
|
<div className="space-y-2" key={variant}>
|
||||||
|
{inlineNotificationSizes.map((size) => (
|
||||||
|
<InlineNotification
|
||||||
|
size={size as InlineNotificationTheme['size']}
|
||||||
|
variant={variant as InlineNotificationTheme['variant']}
|
||||||
|
key={`${variant}-${size}`}
|
||||||
|
title="Notification title goes here"
|
||||||
|
description="Description goes here"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
57
packages/frontend/src/pages/components/renders/input.tsx
Normal file
57
packages/frontend/src/pages/components/renders/input.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Input } from 'components/shared/Input';
|
||||||
|
import { SearchIcon, CrossIcon } from 'components/shared/CustomIcon';
|
||||||
|
|
||||||
|
export const renderInputs = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex w-full gap-10">
|
||||||
|
<Input
|
||||||
|
label="Label"
|
||||||
|
description="Additional information or context"
|
||||||
|
leftIcon={<SearchIcon />}
|
||||||
|
rightIcon={<CrossIcon />}
|
||||||
|
placeholder="Placeholder text"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
label="Label"
|
||||||
|
description="Additional information or context"
|
||||||
|
placeholder="Placeholder text"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
state="error"
|
||||||
|
label="Label"
|
||||||
|
description="Additional information or context"
|
||||||
|
placeholder="Placeholder text"
|
||||||
|
helperText="The error goes here"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full gap-10">
|
||||||
|
<Input
|
||||||
|
label="Label"
|
||||||
|
leftIcon={<SearchIcon />}
|
||||||
|
rightIcon={<CrossIcon />}
|
||||||
|
description="Additional information or context"
|
||||||
|
placeholder="Placeholder text"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
label="Label"
|
||||||
|
description="Additional information or context"
|
||||||
|
placeholder="Placeholder text"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
state="error"
|
||||||
|
label="Label"
|
||||||
|
description="Additional information or context"
|
||||||
|
placeholder="Placeholder text"
|
||||||
|
size="sm"
|
||||||
|
helperText="The error goes here"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
13
packages/frontend/src/utils/classnames.ts
Normal file
13
packages/frontend/src/utils/classnames.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { clsx } from 'clsx';
|
||||||
|
import type { ClassValue } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a merged class name string by merging and processing multiple class names and Tailwind CSS styles.
|
||||||
|
*
|
||||||
|
* @param {...string[]} args - One or more class names and/or Tailwind CSS styles to be merged.
|
||||||
|
* @returns {string} - The merged class name string.
|
||||||
|
*/
|
||||||
|
export function cn(...args: ClassValue[]): string {
|
||||||
|
return twMerge(clsx(args));
|
||||||
|
}
|
@ -6382,7 +6382,7 @@ clone@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||||
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
||||||
|
|
||||||
clsx@^2.0.0:
|
clsx@^2.0.0, clsx@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
|
||||||
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
|
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
|
||||||
|
Loading…
Reference in New Issue
Block a user