Compare commits

...
Author SHA1 Message Date
Wahyu Kurniawan 557e7ec5ce Merge branch 'andrehadianto/design-system-components' of https://github.com/snowball-tools/snowballtools-base into ayungavis/T-4864-radio 2024-02-22 10:46:45 +07:00
Wahyu Kurniawan 6df094bf2e [T-4861: feat] Inline notification component (#86)
* ️ feat: create info square icon component

* ️ feat: create inline notification component

* 📝 docs: add js doc comment and add inline notification component to the example page

* 🐛 fix: use the right method of `useCallback
2024-02-22 10:45:19 +07:00
Andre Hadianto 2369f4498a Merge pull request #85 from snowball-tools/andrehadianto/T-4863-input-field
feat: input field
2024-02-22 10:18:59 +08:00
Wahyu Kurniawan eb6a727425 [T-4862: feat] Link component (#88) 2024-02-22 09:17:22 +07:00
Andre H da2f7ede42 🔧 chore: add dependency to usememo 2024-02-22 09:15:17 +07:00
Wahyu Kurniawan dd099d7b94 🔧 chore: install @radix-ui/react-radio-group 2024-02-22 01:02:37 +07:00
Wahyu Kurniawan a17d5cfe3d 📝 docs: add radio component to the example page 2024-02-22 01:02:10 +07:00
Wahyu Kurniawan cbc22fe16f ️ feat: create radio component 2024-02-22 01:01:49 +07:00
Wahyu Kurniawan ac260dd59c 🎨 style: make the cursor of the tab trigger wrapper to default 2024-02-21 22:07:34 +07:00
Andre H 448d0ceb7c 🔧 chore: 1 classname per object 2024-02-21 17:10:45 +07:00
Andre H 195d957251 🔧 chore: restructure tailwind classnames 2024-02-21 17:08:33 +07:00
Andre H 807a4b1197 🔧 chore: lint 2024-02-21 17:08:14 +07:00
Andre H 5f6723fad6 ♻️ refactor: create new renderInput for component page 2024-02-21 17:07:59 +07:00
Andre H b663067035 🔧 chore: use absolute import 2024-02-21 17:07:30 +07:00
Andre Hadianto b3a99475af Merge branch 'andrehadianto/design-system-components' into andrehadianto/T-4863-input-field 2024-02-21 17:39:29 +08:00
Andre H 7d5963d776 🔧 chore: add input to page/components 2024-02-21 15:44:21 +07:00
Andre H 33b6191539 ️ feat: implement Input Field component 2024-02-21 15:43:53 +07:00
Andre H 636f68d7a4 🔧 chore: add more icons 2024-02-21 15:43:32 +07:00
Andre H 237e9e5cb9 🔧 chore: implement classnames utils 2024-02-21 15:43:09 +07:00
Andre H e70bb34190 ⚒️ build: reorder packages 2024-02-21 15:42:35 +07:00
25 changed files with 867 additions and 3 deletions
+2
View File
@@ -7,6 +7,7 @@
"@material-tailwind/react": "^2.1.7",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-tabs": "^1.0.4",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
@@ -16,6 +17,7 @@
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"assert": "^2.1.0",
"clsx": "^2.1.0",
"date-fns": "^3.3.1",
"downshift": "^8.2.3",
"eslint-config-react-app": "^7.0.1",
@@ -118,6 +118,29 @@ export const buttonTheme = tv(
'disabled:border-transparent',
'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: [],
},
},
@@ -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,4 +4,8 @@ export * from './CheckIcon';
export * from './ChevronGrabberHorizontal';
export * from './ChevronLeft';
export * from './ChevronRight';
export * from './InfoSquareIcon';
export * from './WarningIcon';
export * from './SearchIcon';
export * from './CrossIcon';
export * from './GlobeIcon';
@@ -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';
@@ -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>;
@@ -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>
);
};
@@ -0,0 +1,2 @@
export * from './Input';
export * from './Input.theme';
@@ -0,0 +1,54 @@
import { VariantProps, tv } from 'tailwind-variants';
export const radioTheme = tv({
slots: {
root: ['flex', 'gap-3', 'flex-wrap'],
wrapper: ['flex', 'items-center', 'gap-2', 'group'],
label: ['text-sm', 'tracking-[-0.006em]', 'text-elements-high-em'],
radio: [
'w-5',
'h-5',
'rounded-full',
'border',
'group',
'border-border-interactive/10',
'shadow-button',
'group-hover:border-border-interactive/[0.14]',
'focus-ring',
// Checked
'data-[state=checked]:bg-controls-primary',
'data-[state=checked]:group-hover:bg-controls-primary-hovered',
],
indicator: [
'flex',
'items-center',
'justify-center',
'relative',
'w-full',
'h-full',
'after:content-[""]',
'after:block',
'after:w-2.5',
'after:h-2.5',
'after:rounded-full',
'after:bg-transparent',
'after:group-hover:bg-controls-disabled',
'after:group-focus-visible:bg-controls-disabled',
// Checked
'after:data-[state=checked]:bg-elements-on-primary',
'after:data-[state=checked]:group-hover:bg-elements-on-primary',
'after:data-[state=checked]:group-focus-visible:bg-elements-on-primary',
],
},
variants: {
orientation: {
vertical: { root: ['flex-col'] },
horizontal: { root: ['flex-row'] },
},
},
defaultVariants: {
orientation: 'vertical',
},
});
export type RadioTheme = VariantProps<typeof radioTheme>;
@@ -0,0 +1,63 @@
import React from 'react';
import {
Root as RadixRoot,
RadioGroupProps,
} from '@radix-ui/react-radio-group';
import { RadioTheme, radioTheme } from './Radio.theme';
import { RadioItem, RadioItemProps } from './RadioItem';
export interface RadioOption extends RadioItemProps {
/**
* The label of the radio option.
*/
label: string;
/**
* The value of the radio option.
*/
value: string;
}
export interface RadioProps extends RadioGroupProps, RadioTheme {
/**
* The options of the radio.
* @default []
* @example
* ```tsx
* const options = [
* {
* label: 'Label 1',
* value: '1',
* },
* {
* label: 'Label 2',
* value: '2',
* },
* {
* label: 'Label 3',
* value: '3',
* },
* ];
* ```
*/
options: RadioOption[];
}
/**
* The Radio component is used to select one option from a list of options.
*/
export const Radio = ({
className,
options,
orientation,
...props
}: RadioProps) => {
const { root } = radioTheme({ orientation });
return (
<RadixRoot {...props} className={root({ className })}>
{options.map((option) => (
<RadioItem key={option.value} {...option} />
))}
</RadixRoot>
);
};
@@ -0,0 +1,74 @@
import React, { ComponentPropsWithoutRef } from 'react';
import {
Item as RadixRadio,
Indicator as RadixIndicator,
RadioGroupItemProps,
RadioGroupIndicatorProps,
} from '@radix-ui/react-radio-group';
import { radioTheme } from './Radio.theme';
export interface RadioItemProps extends RadioGroupItemProps {
/**
* The wrapper props of the radio item.
* You can use this prop to customize the wrapper props.
*/
wrapperProps?: ComponentPropsWithoutRef<'div'>;
/**
* The label props of the radio item.
* You can use this prop to customize the label props.
*/
labelProps?: ComponentPropsWithoutRef<'label'>;
/**
* The indicator props of the radio item.
* You can use this prop to customize the indicator props.
*/
indicatorProps?: RadioGroupIndicatorProps;
/**
* The id of the radio item.
*/
id?: string;
/**
* The label of the radio item.
*/
label?: string;
}
/**
* The RadioItem component is used to render a radio item.
*/
export const RadioItem = ({
className,
wrapperProps,
labelProps,
indicatorProps,
label,
id,
...props
}: RadioItemProps) => {
const { wrapper, label: labelClass, radio, indicator } = radioTheme();
// Generate a unique id for the radio item from the label if the id is not provided
const kebabCaseLabel = label?.toLowerCase().replace(/\s+/g, '-');
const componentId = id ?? kebabCaseLabel;
return (
<div className={wrapper({ className: wrapperProps?.className })}>
<RadixRadio {...props} className={radio({ className })} id={componentId}>
<RadixIndicator
forceMount
{...indicatorProps}
className={indicator({ className: indicatorProps?.className })}
/>
</RadixRadio>
{label && (
<label
{...labelProps}
className={labelClass({ className: labelProps?.className })}
htmlFor={componentId}
>
{label}
</label>
)}
</div>
);
};
@@ -0,0 +1,2 @@
export * from './Radio';
export * from './RadioItem';
@@ -9,6 +9,8 @@ export const tabsTheme = tv({
// Horizontal default
'px-1',
'pb-5',
'cursor-default',
'select-none',
'text-elements-low-em',
'border-b-2',
'border-transparent',
@@ -7,20 +7,32 @@ import {
} from './renders/checkbox';
import { avatars, avatarsFallback } from './renders/avatar';
import { renderBadges } from './renders/badge';
import { renderButtonIcons, renderButtons } from './renders/button';
import {
renderButtonIcons,
renderButtons,
renderLinks,
} from './renders/button';
import {
renderTabWithBadges,
renderTabs,
renderVerticalTabs,
} from './renders/tabs';
import { RADIO_OPTIONS } from './renders/radio';
import { Radio } from 'components/shared/Radio';
import {
renderInlineNotificationWithDescriptions,
renderInlineNotifications,
} from './renders/inlineNotifications';
import { renderInputs } from './renders/input';
const Page = () => {
const [singleDate, setSingleDate] = useState<Value>();
const [dateRange, setDateRange] = useState<Value>();
const [selectedRadio, setSelectedRadio] = useState<string>('');
return (
<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>
<p className="mt-4 text-lg text-center text-gray-500">
Get started by editing{' '}
@@ -33,6 +45,11 @@ const Page = () => {
{/* Button */}
<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>
<div className="flex flex-col gap-10">
{renderButtons()}
@@ -112,6 +129,50 @@ const Page = () => {
<h1 className="text-2xl font-bold">Vertical Tabs</h1>
{renderVerticalTabs()}
</div>
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Radio */}
<div className="flex flex-col gap-10 items-center justify-between">
<h1 className="text-2xl font-bold">Radio</h1>
<div className="flex gap-20 items-start">
<Radio
options={RADIO_OPTIONS}
orientation="vertical"
value={selectedRadio}
onValueChange={setSelectedRadio}
/>
<Radio
options={RADIO_OPTIONS}
orientation="horizontal"
value={selectedRadio}
onValueChange={setSelectedRadio}
/>
</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>
@@ -47,3 +47,19 @@ export const renderButtonIcons = () => {
</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>
));
};
@@ -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>
</>
);
};
@@ -0,0 +1,16 @@
import { RadioOption } from 'components/shared/Radio';
export const RADIO_OPTIONS: RadioOption[] = [
{
label: 'Label 1',
value: '1',
},
{
label: 'Label 2',
value: '2',
},
{
label: 'Label 3',
value: '3',
},
];
+13
View 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));
}
+18 -1
View File
@@ -3367,6 +3367,23 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "1.0.2"
"@radix-ui/react-radio-group@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz#3197f5dcce143bcbf961471bf89320735c0212d3"
integrity sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-direction" "1.0.1"
"@radix-ui/react-presence" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-roving-focus" "1.0.4"
"@radix-ui/react-use-controllable-state" "1.0.1"
"@radix-ui/react-use-previous" "1.0.1"
"@radix-ui/react-use-size" "1.0.1"
"@radix-ui/react-roving-focus@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974"
@@ -6327,7 +6344,7 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
clsx@^2.0.0:
clsx@^2.0.0, clsx@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==