first commit
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"ui": "src/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@ufb/eslint-config/base';
|
||||
import reactConfig from '@ufb/eslint-config/react';
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [
|
||||
{
|
||||
ignores: ['postcss.js'],
|
||||
},
|
||||
...baseConfig,
|
||||
...reactConfig,
|
||||
];
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@ufb/react",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-menubar": "^1.1.16",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-toast": "^1.2.15",
|
||||
"@radix-ui/react-toggle": "^1.1.10",
|
||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@remixicon/react": "^4.9.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.577.0",
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-hook-form": "^7.72.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@ufb/eslint-config": "workspace:^",
|
||||
"@ufb/prettier-config": "workspace:^",
|
||||
"@ufb/tailwindcss": "workspace:^",
|
||||
"@ufb/tsconfig": "workspace:^",
|
||||
"eslint": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"react": "^19.2.4",
|
||||
"tailwindcss": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"prettier": "@ufb/prettier-config"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } };
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
import { Slottable } from '@radix-ui/react-slot';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { ICON_SIZE } from '../constants';
|
||||
import { cn } from '../lib/utils';
|
||||
import type { Size } from '../types';
|
||||
import { Icon } from './icon';
|
||||
|
||||
const DefaultValue = {
|
||||
iconSize: 'small',
|
||||
iconAlign: 'right',
|
||||
border: false,
|
||||
} as const;
|
||||
|
||||
const AccordionContext = React.createContext<{
|
||||
iconSize: Size;
|
||||
iconAlign: 'left' | 'right';
|
||||
}>({
|
||||
iconSize: DefaultValue.iconSize,
|
||||
iconAlign: DefaultValue.iconAlign,
|
||||
});
|
||||
|
||||
const Accordion = React.forwardRef<
|
||||
React.ComponentRef<typeof AccordionPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root> & {
|
||||
iconSize?: Size;
|
||||
iconAlign?: 'left' | 'right';
|
||||
border?: boolean;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
iconAlign = DefaultValue.iconAlign,
|
||||
iconSize = DefaultValue.iconSize,
|
||||
border = DefaultValue.border,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => (
|
||||
<AccordionContext.Provider value={{ iconAlign, iconSize }}>
|
||||
<AccordionPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn('accordion', border && 'accordion-border', className)}
|
||||
{...props}
|
||||
/>
|
||||
</AccordionContext.Provider>
|
||||
),
|
||||
);
|
||||
Accordion.displayName = 'Accordion';
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ComponentRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> & {
|
||||
divider?: boolean;
|
||||
}
|
||||
>(({ divider = true, className, ...props }, ref) => {
|
||||
return (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'accordion-item',
|
||||
divider && 'accordion-item-border',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
AccordionItem.displayName = 'AccordionItem';
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { iconSize, iconAlign } = React.useContext(AccordionContext);
|
||||
return (
|
||||
<AccordionPrimitive.Header>
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'accordion-trigger',
|
||||
iconAlign === 'left' && 'accordion-trigger-align-left',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{iconAlign === 'left' && (
|
||||
<Icon name="RiArrowDownSLine" size={ICON_SIZE[iconSize]} />
|
||||
)}
|
||||
<Slottable>{children}</Slottable>
|
||||
{iconAlign === 'right' && (
|
||||
<Icon name="RiArrowDownSLine" size={ICON_SIZE[iconSize]} />
|
||||
)}
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
);
|
||||
});
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const accordionContentVariants = cva('accordion-content', {
|
||||
variants: {
|
||||
iconAlign: {
|
||||
left: '',
|
||||
right: '',
|
||||
},
|
||||
iconSize: {
|
||||
small: '',
|
||||
medium: '',
|
||||
large: '',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
iconAlign: 'left',
|
||||
iconSize: 'small',
|
||||
className: 'accordion-content-inset-small',
|
||||
},
|
||||
{
|
||||
iconAlign: 'left',
|
||||
iconSize: 'medium',
|
||||
className: 'accordion-content-inset-medium',
|
||||
},
|
||||
{
|
||||
iconAlign: 'left',
|
||||
iconSize: 'large',
|
||||
className: 'accordion-content-inset-large',
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
iconAlign: DefaultValue.iconAlign,
|
||||
iconSize: DefaultValue.iconSize,
|
||||
},
|
||||
});
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ComponentRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { iconAlign, iconSize } = React.useContext(AccordionContext);
|
||||
return (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="accordion-content-box"
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
accordionContentVariants({ iconAlign, iconSize, className }),
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</AccordionPrimitive.Content>
|
||||
);
|
||||
});
|
||||
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { ALERT_DEFAULT_ICON } from '../constants';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Button } from './button';
|
||||
import type { IconNameType } from './icon';
|
||||
import { Icon } from './icon';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const alertVariants = cva('alert', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'alert-default',
|
||||
warning: 'alert-warning',
|
||||
success: 'alert-success',
|
||||
error: 'alert-error',
|
||||
informative: 'alert-informative',
|
||||
},
|
||||
radius: {
|
||||
small: 'alert-radius-small',
|
||||
medium: 'alert-radius-medium',
|
||||
large: 'alert-radius-large',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
radius: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const AlertContext = React.createContext<VariantProps<typeof alertVariants>>({
|
||||
variant: 'default',
|
||||
radius: undefined,
|
||||
});
|
||||
|
||||
interface AlertProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof alertVariants> {}
|
||||
|
||||
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
||||
({ children, className, variant = 'default', radius, ...props }, ref) => {
|
||||
const { themeRadius } = useTheme();
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="alert"
|
||||
className={cn(
|
||||
alertVariants({ variant, radius: radius ?? themeRadius }),
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<AlertContext.Provider
|
||||
value={{ variant, radius: radius ?? themeRadius }}
|
||||
>
|
||||
{children}
|
||||
</AlertContext.Provider>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
Alert.displayName = 'Alert';
|
||||
|
||||
type AlertContentProps = React.HTMLAttributes<HTMLHeadingElement>;
|
||||
|
||||
const AlertContent = React.forwardRef<HTMLParagraphElement, AlertContentProps>(
|
||||
({ children, className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('alert-content', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
);
|
||||
AlertContent.displayName = 'AlertContent';
|
||||
|
||||
type AlertTextContainerProps = React.HTMLAttributes<HTMLHeadingElement>;
|
||||
|
||||
const AlertTextContainer = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
AlertTextContainerProps
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('alert-text-container', className)} {...props} />
|
||||
));
|
||||
AlertTextContainer.displayName = 'AlertTextContainer';
|
||||
|
||||
type AlertTitleProps = React.HTMLAttributes<HTMLHeadingElement>;
|
||||
|
||||
const AlertTitle = React.forwardRef<HTMLParagraphElement, AlertTitleProps>(
|
||||
({ className, ...props }, ref) => (
|
||||
<h5 ref={ref} className={cn('alert-title', className)} {...props} />
|
||||
),
|
||||
);
|
||||
AlertTitle.displayName = 'AlertTitle';
|
||||
|
||||
type AlertDescriptionProps = React.HTMLAttributes<HTMLHeadingElement>;
|
||||
|
||||
const AlertDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
AlertDescriptionProps
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p className={cn('alert-description', className)} {...props} ref={ref} />
|
||||
));
|
||||
AlertDescription.displayName = 'AlertDescription';
|
||||
|
||||
interface AlertIconButtonProps
|
||||
extends Omit<React.ComponentPropsWithoutRef<typeof Button>, 'icon'> {
|
||||
icon?: IconNameType;
|
||||
}
|
||||
|
||||
const AlertIconButton = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
AlertIconButtonProps
|
||||
>(({ icon, variant, size, className, ...props }, ref) => {
|
||||
const { radius } = React.useContext(AlertContext);
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
radius={radius ?? 'medium'}
|
||||
size={size ?? 'medium'}
|
||||
variant={variant ?? 'ghost'}
|
||||
className={cn('alert-close', className)}
|
||||
{...props}
|
||||
>
|
||||
<Icon name={icon ?? 'RiCloseFill'} />
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
AlertIconButton.displayName = 'AlertIconButton';
|
||||
|
||||
const AlertButton = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentPropsWithoutRef<typeof Button>
|
||||
>(({ variant, size, className, ...props }, ref) => {
|
||||
const { radius } = React.useContext(AlertContext);
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={variant ?? 'outline'}
|
||||
size={size ?? 'medium'}
|
||||
radius={radius ?? 'medium'}
|
||||
className={cn('alert-button', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
AlertButton.displayName = 'AlertButton';
|
||||
|
||||
const alertIconVariants = cva('alert-icon', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'alert-icon-default',
|
||||
warning: 'alert-icon-warning',
|
||||
success: 'alert-icon-success',
|
||||
error: 'alert-icon-error',
|
||||
informative: 'alert-icon-informative',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
interface AlertIconProps
|
||||
extends Omit<React.ComponentPropsWithoutRef<typeof Icon>, 'name'> {
|
||||
name?: IconNameType;
|
||||
}
|
||||
const AlertIcon = ({
|
||||
className,
|
||||
name = undefined,
|
||||
size = 20,
|
||||
...props
|
||||
}: AlertIconProps) => {
|
||||
const { variant } = React.useContext(AlertContext);
|
||||
return (
|
||||
<Icon
|
||||
className={cn(alertIconVariants({ variant, className }))}
|
||||
name={name ?? ALERT_DEFAULT_ICON[variant ?? 'default']}
|
||||
size={size}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
AlertIcon.displayName = 'AlertIcon';
|
||||
|
||||
export {
|
||||
Alert,
|
||||
AlertContent,
|
||||
AlertTextContainer,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
AlertIconButton,
|
||||
AlertButton,
|
||||
AlertIcon,
|
||||
type AlertProps,
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import type { Color, Radius } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
type BadgeVariant = 'bold' | 'subtle' | 'outline';
|
||||
|
||||
const badgeVariants = cva('badge', {
|
||||
variants: {
|
||||
radius: {
|
||||
large: 'badge-radius-large',
|
||||
medium: 'badge-radius-medium',
|
||||
small: 'badge-radius-small',
|
||||
},
|
||||
variant: {
|
||||
bold: '',
|
||||
subtle: '',
|
||||
outline: '',
|
||||
},
|
||||
color: {
|
||||
default: '',
|
||||
blue: '',
|
||||
orange: '',
|
||||
red: '',
|
||||
green: '',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
variant: 'bold',
|
||||
color: 'default',
|
||||
className: 'badge-bold-default',
|
||||
},
|
||||
{
|
||||
variant: 'bold',
|
||||
color: 'blue',
|
||||
className: 'badge-bold-blue',
|
||||
},
|
||||
{
|
||||
variant: 'bold',
|
||||
color: 'orange',
|
||||
className: 'badge-bold-orange',
|
||||
},
|
||||
{
|
||||
variant: 'bold',
|
||||
color: 'red',
|
||||
className: 'badge-bold-red',
|
||||
},
|
||||
{
|
||||
variant: 'bold',
|
||||
color: 'green',
|
||||
className: 'badge-bold-green',
|
||||
},
|
||||
{
|
||||
variant: 'subtle',
|
||||
color: 'default',
|
||||
className: 'badge-subtle-default',
|
||||
},
|
||||
{
|
||||
variant: 'subtle',
|
||||
color: 'blue',
|
||||
className: 'badge-subtle-blue',
|
||||
},
|
||||
{
|
||||
variant: 'subtle',
|
||||
color: 'orange',
|
||||
className: 'badge-subtle-orange',
|
||||
},
|
||||
{
|
||||
variant: 'subtle',
|
||||
color: 'red',
|
||||
className: 'badge-subtle-red',
|
||||
},
|
||||
{
|
||||
variant: 'subtle',
|
||||
color: 'green',
|
||||
className: 'badge-subtle-green',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
color: 'default',
|
||||
className: 'badge-outline-default',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
color: 'blue',
|
||||
className: 'badge-outline-blue',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
color: 'orange',
|
||||
className: 'badge-outline-orange',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
color: 'red',
|
||||
className: 'badge-outline-red',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
color: 'green',
|
||||
className: 'badge-outline-green',
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
radius: undefined,
|
||||
variant: 'bold',
|
||||
color: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {
|
||||
radius?: Radius;
|
||||
variant?: BadgeVariant;
|
||||
color?: Color;
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
|
||||
(
|
||||
{
|
||||
radius,
|
||||
variant = 'bold',
|
||||
color = 'default',
|
||||
className,
|
||||
asChild,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { themeRadius } = useTheme();
|
||||
const Comp = asChild ? Slot : 'div';
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn(
|
||||
badgeVariants({ radius: radius ?? themeRadius, variant, color }),
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Badge.displayName = 'Badge';
|
||||
|
||||
export { Badge, type BadgeProps };
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Slot, Slottable } from '@radix-ui/react-slot';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import type { ButtonVariant, Radius, Size } from '../lib/types';
|
||||
import { cn, composeRefs } from '../lib/utils';
|
||||
import { Spinner } from './spinner';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const defaultVariants: {
|
||||
variant: ButtonVariant;
|
||||
size?: Size;
|
||||
radius?: Radius;
|
||||
loading?: boolean;
|
||||
} = {
|
||||
variant: 'primary',
|
||||
size: undefined,
|
||||
radius: undefined,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
const buttonVariants = cva('button', {
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'button-primary',
|
||||
secondary: 'button-secondary',
|
||||
destructive: 'button-destructive',
|
||||
ghost: 'button-ghost',
|
||||
outline: 'button-outline',
|
||||
},
|
||||
size: {
|
||||
small: 'button-small',
|
||||
medium: 'button-medium',
|
||||
large: 'button-large',
|
||||
},
|
||||
radius: {
|
||||
small: 'button-radius-small',
|
||||
medium: 'button-radius-medium',
|
||||
large: 'button-radius-large',
|
||||
},
|
||||
loading: {
|
||||
true: '!text-transparent [&>*:not(.button-loading)]:!invisible',
|
||||
false: '',
|
||||
},
|
||||
},
|
||||
defaultVariants,
|
||||
});
|
||||
|
||||
const buttonLoadingVariants = cva('button-loading', {
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'button-loading-primary',
|
||||
secondary: 'button-loading-secondary',
|
||||
destructive: 'button-loading-destructive',
|
||||
ghost: 'button-loading-ghost',
|
||||
outline: 'button-loading-outline',
|
||||
},
|
||||
},
|
||||
defaultVariants,
|
||||
});
|
||||
|
||||
interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
variant?: ButtonVariant;
|
||||
size?: Size;
|
||||
radius?: Radius;
|
||||
loading?: boolean;
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
type = 'button',
|
||||
variant = 'primary',
|
||||
size,
|
||||
radius,
|
||||
disabled = false,
|
||||
loading = false,
|
||||
asChild = false,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
const { themeSize, themeRadius } = useTheme();
|
||||
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!buttonRef.current) return;
|
||||
|
||||
const childNodes = buttonRef.current.childNodes;
|
||||
const isSvgOnly = Array.from(childNodes).every(
|
||||
(node) =>
|
||||
((node as HTMLElement).nodeType === Node.ELEMENT_NODE &&
|
||||
(node as HTMLElement).tagName.toLowerCase() === 'svg') ||
|
||||
((node as HTMLElement).nodeType === Node.TEXT_NODE &&
|
||||
!((node as HTMLElement).textContent || '').trim()),
|
||||
);
|
||||
|
||||
if (isSvgOnly) {
|
||||
buttonRef.current.classList.add('svg-only');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Comp
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant,
|
||||
size: size ?? themeSize,
|
||||
radius: radius ?? themeRadius,
|
||||
loading,
|
||||
className,
|
||||
}),
|
||||
)}
|
||||
type={type}
|
||||
disabled={disabled || loading}
|
||||
ref={composeRefs(buttonRef, ref)}
|
||||
{...props}
|
||||
>
|
||||
<Slottable>{children}</Slottable>
|
||||
{loading && (
|
||||
<span className={cn(buttonLoadingVariants({ variant }))}>
|
||||
<Spinner size={size ?? themeSize} />
|
||||
</span>
|
||||
)}
|
||||
</Comp>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export { Button, type ButtonProps, buttonVariants };
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import type { DateRange, DayPickerDefaultProps } from 'react-day-picker';
|
||||
import { DayPicker } from 'react-day-picker';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import { Icon } from './icon';
|
||||
|
||||
type CalendarProps = React.ComponentProps<typeof DayPicker> & {
|
||||
showToday?: boolean;
|
||||
};
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showToday = true,
|
||||
showOutsideDays = true,
|
||||
mode = 'default',
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
mode={mode as DayPickerDefaultProps['mode']}
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn('calendar', className)}
|
||||
classNames={{
|
||||
months: 'calendar-months',
|
||||
month: 'calendar-month',
|
||||
caption: 'calendar-caption',
|
||||
caption_label: 'calendar-caption-label',
|
||||
nav: 'calendar-nav',
|
||||
nav_button: 'calendar-nav-button',
|
||||
nav_button_previous: 'calendar-nav-button-previous',
|
||||
nav_button_next: 'calendar-nav-button-next',
|
||||
table: 'calendar-table',
|
||||
head_row: 'calendar-head-row',
|
||||
head_cell: 'calendar-head-cell',
|
||||
row: 'calendar-row',
|
||||
cell: 'calendar-cell',
|
||||
day: 'calendar-day',
|
||||
day_range_start: 'calendar-day-range-start',
|
||||
day_range_end: 'calendar-day-range-end',
|
||||
day_selected: 'calendar-day-selected',
|
||||
day_today: showToday ? 'calendar-day-today' : '',
|
||||
day_outside: 'calendar-day-outside',
|
||||
day_disabled: 'calendar-day-disabled',
|
||||
day_range_middle: 'calendar-day-range-middle',
|
||||
day_hidden: 'calendar-day-hidden',
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: () => <Icon name="RiArrowLeftSLine" size={16} />,
|
||||
IconRight: () => <Icon name="RiArrowRightSLine" size={16} />,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Calendar.displayName = 'Calendar';
|
||||
|
||||
export { Calendar, type DateRange, type CalendarProps };
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Slot, Slottable } from '@radix-ui/react-slot';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { CAPTION_DEFAULT_ICON, ICON_SIZE } from '../constants';
|
||||
import type { CaptionType } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
import type { IconNameType } from './icon';
|
||||
import { Icon } from './icon';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const captionVariants = cva('caption', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'caption-default',
|
||||
success: 'caption-success',
|
||||
info: 'caption-info',
|
||||
error: 'caption-error',
|
||||
},
|
||||
defaultVariants: { variant: 'default' },
|
||||
},
|
||||
});
|
||||
|
||||
interface CaptionProps extends React.ComponentPropsWithoutRef<'p'> {
|
||||
variant?: CaptionType;
|
||||
icon?: IconNameType;
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Caption = React.forwardRef<HTMLParagraphElement, CaptionProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
icon = undefined,
|
||||
variant,
|
||||
className,
|
||||
children,
|
||||
asChild,
|
||||
...rest
|
||||
} = props;
|
||||
const Comp = asChild ? Slot : 'p';
|
||||
const isError = variant === 'error';
|
||||
const { themeSize } = useTheme();
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn(
|
||||
captionVariants({ variant: variant ?? 'default', className }),
|
||||
)}
|
||||
data-error={isError}
|
||||
{...rest}
|
||||
>
|
||||
<Icon
|
||||
name={
|
||||
icon ??
|
||||
CAPTION_DEFAULT_ICON[isError ? 'error' : (variant ?? 'default')]
|
||||
}
|
||||
size={ICON_SIZE[themeSize]}
|
||||
className="caption-icon"
|
||||
/>
|
||||
<Slottable>{children}</Slottable>
|
||||
</Comp>
|
||||
);
|
||||
},
|
||||
);
|
||||
Caption.displayName = 'Caption';
|
||||
|
||||
export { Caption };
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||
import { Slottable } from '@radix-ui/react-slot';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { CHECK_ICON_SIZE } from '../constants';
|
||||
import type { Size } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Icon } from './icon';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const defaultVariants: {
|
||||
size?: Size;
|
||||
} = {
|
||||
size: undefined,
|
||||
};
|
||||
|
||||
const checkboxVariants = cva('checkbox', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'checkbox-small',
|
||||
medium: 'checkbox-medium',
|
||||
large: 'checkbox-large',
|
||||
},
|
||||
},
|
||||
defaultVariants,
|
||||
});
|
||||
|
||||
const checkVariants = cva('check', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'check-small',
|
||||
medium: 'check-medium',
|
||||
large: 'check-large',
|
||||
},
|
||||
},
|
||||
defaultVariants,
|
||||
});
|
||||
|
||||
interface CheckboxProps
|
||||
extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {
|
||||
size?: Size;
|
||||
}
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
CheckboxProps
|
||||
>(({ checked, size, children, className, onCheckedChange, ...props }, ref) => {
|
||||
const { themeSize } = useTheme();
|
||||
const [currentChecked, setCurrentChecked] =
|
||||
React.useState<CheckboxPrimitive.CheckedState>(false);
|
||||
|
||||
const handleCheckedChange = (checked: CheckboxPrimitive.CheckedState) => {
|
||||
setCurrentChecked(checked);
|
||||
onCheckedChange?.(checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(checkboxVariants({ size: size ?? themeSize, className }))}
|
||||
checked={checked ?? currentChecked}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
{...props}
|
||||
>
|
||||
<span className={cn(checkVariants({ size }))}>
|
||||
<CheckboxPrimitive.Indicator className={cn('checkbox-icon')}>
|
||||
<Icon
|
||||
name={
|
||||
(checked ?? currentChecked) === 'indeterminate' ?
|
||||
'RiSubtractLine'
|
||||
: 'RiCheckLine'
|
||||
}
|
||||
size={CHECK_ICON_SIZE[size ?? themeSize]}
|
||||
/>
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</span>
|
||||
<Slottable>{children}</Slottable>
|
||||
</CheckboxPrimitive.Root>
|
||||
);
|
||||
});
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||
|
||||
export { Checkbox, type CheckboxProps };
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Slottable } from '@radix-ui/react-slot';
|
||||
import { Command as CommandPrimitive } from 'cmdk';
|
||||
|
||||
import type { TriggerType } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Button } from './button';
|
||||
import { Icon } from './icon';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './popover';
|
||||
import { ScrollArea, ScrollBar } from './scroll-area';
|
||||
|
||||
const ComboboxContext = React.createContext<{
|
||||
trigger: TriggerType;
|
||||
isHover: boolean;
|
||||
setTrigger: React.Dispatch<React.SetStateAction<TriggerType>>;
|
||||
setIsHover: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}>({
|
||||
trigger: 'click',
|
||||
setTrigger: () => 'click',
|
||||
isHover: false,
|
||||
setIsHover: () => false,
|
||||
});
|
||||
|
||||
const Combobox = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof Popover>) => {
|
||||
const [trigger, setTrigger] = React.useState<TriggerType>('click');
|
||||
const [isHover, setIsHover] = React.useState(false);
|
||||
|
||||
return (
|
||||
<ComboboxContext.Provider
|
||||
value={{ trigger, setTrigger, isHover, setIsHover }}
|
||||
>
|
||||
<Popover
|
||||
{...props}
|
||||
open={trigger === 'hover' ? !!open || isHover : open}
|
||||
onOpenChange={(open: boolean) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(open);
|
||||
}
|
||||
onOpenChange?.(open);
|
||||
}}
|
||||
/>
|
||||
</ComboboxContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const ComboboxContent = React.forwardRef<
|
||||
React.ComponentRef<typeof PopoverContent>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverContent> & {
|
||||
options?: React.ComponentPropsWithoutRef<typeof CommandPrimitive>;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{ className, children, onMouseEnter, onMouseLeave, options, ...props },
|
||||
ref,
|
||||
) => {
|
||||
const { trigger, setIsHover } = React.useContext(ComboboxContext);
|
||||
|
||||
const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(true);
|
||||
}
|
||||
onMouseEnter?.(e);
|
||||
};
|
||||
|
||||
const handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(false);
|
||||
}
|
||||
onMouseLeave?.(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<PopoverContent
|
||||
ref={ref}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
{...props}
|
||||
className={cn('combobox-content', className)}
|
||||
>
|
||||
<CommandPrimitive {...options}>{children}</CommandPrimitive>
|
||||
</PopoverContent>
|
||||
);
|
||||
},
|
||||
);
|
||||
ComboboxContent.displayName = CommandPrimitive.displayName;
|
||||
|
||||
interface ComboboxInputProps
|
||||
extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> {
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ComboboxInput = React.forwardRef<
|
||||
React.ComponentRef<typeof CommandPrimitive.Input>,
|
||||
ComboboxInputProps
|
||||
>(({ icon, className, ...props }, ref) => (
|
||||
<div className="combobox-input-box" cmdk-input-wrapper="">
|
||||
{icon ?? <Icon name="RiSearchLine" size={16} />}
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn('combobox-input', className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
||||
ComboboxInput.displayName = CommandPrimitive.Input.displayName;
|
||||
|
||||
const ComboboxList = React.forwardRef<
|
||||
React.ComponentRef<typeof CommandPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> & {
|
||||
maxHeight?: string;
|
||||
}
|
||||
>(({ maxHeight, className, ...props }, ref) => (
|
||||
<ScrollArea maxHeight={maxHeight}>
|
||||
<CommandPrimitive.List
|
||||
ref={ref}
|
||||
className={cn('combobox-list', className)}
|
||||
{...props}
|
||||
/>
|
||||
<ScrollBar />
|
||||
</ScrollArea>
|
||||
));
|
||||
|
||||
ComboboxList.displayName = CommandPrimitive.List.displayName;
|
||||
|
||||
const ComboboxEmpty = React.forwardRef<
|
||||
React.ComponentRef<typeof CommandPrimitive.Empty>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||
>((props, ref) => (
|
||||
<CommandPrimitive.Empty ref={ref} className="combobox-empty" {...props} />
|
||||
));
|
||||
|
||||
ComboboxEmpty.displayName = CommandPrimitive.Empty.displayName;
|
||||
|
||||
const ComboboxGroup = React.forwardRef<
|
||||
React.ComponentRef<typeof CommandPrimitive.Group>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Group
|
||||
ref={ref}
|
||||
className={cn('combobox-group', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
ComboboxGroup.displayName = CommandPrimitive.Group.displayName;
|
||||
|
||||
const ComboboxSeparator = React.forwardRef<
|
||||
React.ComponentRef<typeof CommandPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('combobox-separator', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ComboboxSeparator.displayName = CommandPrimitive.Separator.displayName;
|
||||
|
||||
const ComboboxItem = React.forwardRef<
|
||||
React.ComponentRef<typeof CommandPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||
>(({ children, className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn('combobox-item', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</CommandPrimitive.Item>
|
||||
));
|
||||
|
||||
ComboboxItem.displayName = CommandPrimitive.Item.displayName;
|
||||
|
||||
const ComboboxCaption = React.forwardRef<
|
||||
React.ComponentRef<'span'>,
|
||||
React.ComponentPropsWithoutRef<'span'>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<span ref={ref} className={cn('combobox-caption', className)} {...props} />
|
||||
));
|
||||
|
||||
ComboboxCaption.displayName = 'ComboboxCaption';
|
||||
|
||||
const ComboboxSelectItem = React.forwardRef<
|
||||
React.ComponentRef<typeof CommandPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> & {
|
||||
checked?: boolean;
|
||||
}
|
||||
>(({ checked, children, className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn('combobox-item', className)}
|
||||
{...props}
|
||||
>
|
||||
<Icon
|
||||
name="RiCheckLine"
|
||||
size={20}
|
||||
color={checked ? 'currentColor' : 'transparent'}
|
||||
className="combobox-check"
|
||||
/>
|
||||
<Slottable>{children}</Slottable>
|
||||
</CommandPrimitive.Item>
|
||||
));
|
||||
|
||||
ComboboxSelectItem.displayName = 'ComboboxSelectItem';
|
||||
|
||||
const ComboboxTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof Button>,
|
||||
React.ComponentPropsWithoutRef<typeof Button> & {
|
||||
trigger?: TriggerType;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
variant = 'outline',
|
||||
trigger,
|
||||
className,
|
||||
children,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { setTrigger, setIsHover } = React.useContext(ComboboxContext);
|
||||
|
||||
const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(true);
|
||||
}
|
||||
onMouseEnter?.(e);
|
||||
};
|
||||
|
||||
const handleMouseLeave = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(false);
|
||||
}
|
||||
onMouseLeave?.(e);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (trigger) {
|
||||
setTrigger(trigger);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (props.asChild) {
|
||||
return (
|
||||
<PopoverTrigger
|
||||
className={className}
|
||||
ref={ref}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</PopoverTrigger>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
className={cn('combobox-trigger', className)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
);
|
||||
},
|
||||
);
|
||||
ComboboxTrigger.displayName = 'ComboboxTrigger';
|
||||
|
||||
export {
|
||||
Combobox,
|
||||
ComboboxContent,
|
||||
ComboboxCaption,
|
||||
ComboboxInput,
|
||||
ComboboxList,
|
||||
ComboboxEmpty,
|
||||
ComboboxGroup,
|
||||
ComboboxItem,
|
||||
ComboboxSelectItem,
|
||||
ComboboxSeparator,
|
||||
ComboboxTrigger,
|
||||
};
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { ALERT_DEFAULT_ICON } from '../constants';
|
||||
import { cn } from '../lib/utils';
|
||||
import type { Radius } from '../types';
|
||||
import { Button } from './button';
|
||||
import { Icon } from './icon';
|
||||
import { ScrollArea, ScrollBar } from './scroll-area';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const DialogTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Trigger>,
|
||||
DialogPrimitive.DialogTriggerProps &
|
||||
React.ComponentPropsWithoutRef<typeof Button>
|
||||
>(({ variant = 'outline', children, ...props }, ref) => {
|
||||
if (props.asChild) {
|
||||
return (
|
||||
<DialogPrimitive.Trigger ref={ref} {...props}>
|
||||
{children}
|
||||
</DialogPrimitive.Trigger>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogPrimitive.Trigger asChild>
|
||||
<Button variant={variant} ref={ref} {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
</DialogPrimitive.Trigger>
|
||||
);
|
||||
});
|
||||
DialogTrigger.displayName = DialogPrimitive.DialogTrigger.displayName;
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
const DialogClose = React.forwardRef<
|
||||
React.ElementRef<typeof Button>,
|
||||
React.ComponentPropsWithoutRef<typeof Button>
|
||||
>(({ variant, ...props }, ref) => (
|
||||
<DialogPrimitive.Close asChild>
|
||||
<Button ref={ref} variant={variant ?? 'outline'} {...props} />
|
||||
</DialogPrimitive.Close>
|
||||
));
|
||||
DialogClose.displayName = 'DialogClose';
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn('dialog', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const dialogContentVariants = cva('dialog-content', {
|
||||
variants: {
|
||||
radius: {
|
||||
large: 'dialog-content-radius-large',
|
||||
medium: 'dialog-content-radius-medium',
|
||||
small: 'dialog-content-radius-small',
|
||||
},
|
||||
defaultVariants: {
|
||||
radius: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interface DialogContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
||||
radius?: Radius;
|
||||
}
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
DialogContentProps
|
||||
>(({ radius, className, children, ...props }, ref) => {
|
||||
const { themeRadius } = useTheme();
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogOverlay onClick={(e) => e.stopPropagation()} />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
dialogContentVariants({ radius: radius ?? themeRadius, className }),
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close asChild>
|
||||
<Button
|
||||
size="medium"
|
||||
variant="ghost"
|
||||
className="dialog-close"
|
||||
aria-label="Close"
|
||||
>
|
||||
<Icon name="RiCloseLine" />
|
||||
</Button>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
});
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const dialogIconVariants = cva('dialog-icon', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'dialog-icon-default',
|
||||
warning: 'dialog-icon-warning',
|
||||
success: 'dialog-icon-success',
|
||||
error: 'dialog-icon-error',
|
||||
informative: 'dialog-icon-informative',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
const DialogIcon = (
|
||||
props: React.ComponentPropsWithoutRef<typeof Icon> &
|
||||
VariantProps<typeof dialogIconVariants>,
|
||||
) => {
|
||||
const { className, name, size = 32, variant, ...rest } = props;
|
||||
return (
|
||||
<Icon
|
||||
{...rest}
|
||||
name={name ?? ALERT_DEFAULT_ICON[variant ?? 'default']}
|
||||
size={size}
|
||||
className={cn(dialogIconVariants({ variant, className }))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
DialogIcon.displayName = 'DialogIcon';
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn('dialog-header', className)} {...props} />
|
||||
);
|
||||
DialogHeader.displayName = 'DialogHeader';
|
||||
|
||||
const dialogFooterVariants = cva('dialog-footer', {
|
||||
variants: {
|
||||
align: {
|
||||
left: 'dialog-footer-left',
|
||||
right: 'dialog-footer-right',
|
||||
center: 'dialog-footer-center',
|
||||
between: 'dialog-footer-between',
|
||||
full: 'dialog-footer-full',
|
||||
},
|
||||
defaultVariants: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interface DialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
const DialogBody = ({ asChild, className, ...props }: DialogBodyProps) => {
|
||||
const Comp = asChild ? Slot : 'div';
|
||||
return (
|
||||
<ScrollArea>
|
||||
<Comp className={cn('dialog-body', className)} {...props} />
|
||||
<ScrollBar />
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
DialogBody.displayName = 'DialogBody';
|
||||
|
||||
interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
align?: 'left' | 'right' | 'center' | 'between' | 'full';
|
||||
}
|
||||
|
||||
const DialogFooter = ({
|
||||
align = 'right',
|
||||
className,
|
||||
...props
|
||||
}: DialogFooterProps) => (
|
||||
<div className={cn(dialogFooterVariants({ align, className }))} {...props} />
|
||||
);
|
||||
DialogFooter.displayName = 'DialogFooter';
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('dialog-title', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('dialog-description', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogIcon,
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
const dividerVariants = cva('divider', {
|
||||
variants: {
|
||||
variant: {
|
||||
bold: 'divider-bold',
|
||||
subtle: 'divider-subtle',
|
||||
},
|
||||
orientation: {
|
||||
horizontal: 'divider-horizontal',
|
||||
vertical: 'divider-vertical',
|
||||
},
|
||||
indent: {
|
||||
0: '',
|
||||
8: '',
|
||||
16: '',
|
||||
24: '',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
orientation: 'horizontal',
|
||||
indent: 8,
|
||||
className: 'divider-horizontal-indent-8',
|
||||
},
|
||||
{
|
||||
orientation: 'horizontal',
|
||||
indent: 16,
|
||||
className: 'divider-horizontal-indent-16',
|
||||
},
|
||||
{
|
||||
orientation: 'horizontal',
|
||||
indent: 24,
|
||||
className: 'divider-horizontal-indent-24',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
indent: 8,
|
||||
className: 'divider-vertical-indent-8',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
indent: 16,
|
||||
className: 'divider-vertical-indent-16',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
indent: 24,
|
||||
className: 'divider-vertical-indent-24',
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
variant: 'bold',
|
||||
orientation: 'horizontal',
|
||||
indent: 0,
|
||||
},
|
||||
});
|
||||
|
||||
interface DividerProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> {
|
||||
variant?: 'bold' | 'subtle';
|
||||
indent?: 0 | 8 | 16 | 24;
|
||||
}
|
||||
|
||||
const Divider = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
DividerProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
variant = 'bold',
|
||||
indent = 0,
|
||||
orientation = 'horizontal',
|
||||
className,
|
||||
decorative = true,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
dividerVariants({ variant, orientation, indent, className }),
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Divider.displayName = SeparatorPrimitive.Root.displayName;
|
||||
|
||||
export { Divider };
|
||||
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import type { DropdownMenuCheckboxItemProps } from '@radix-ui/react-dropdown-menu';
|
||||
import * as DropdownPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import { Slottable } from '@radix-ui/react-slot';
|
||||
|
||||
import type { TriggerType } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Button } from './button';
|
||||
import { Icon } from './icon';
|
||||
import { ScrollArea, ScrollBar } from './scroll-area';
|
||||
|
||||
const DropdownContext = React.createContext<{
|
||||
trigger: TriggerType;
|
||||
isHover: boolean;
|
||||
setTrigger: React.Dispatch<React.SetStateAction<TriggerType>>;
|
||||
setIsHover: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}>({
|
||||
trigger: 'click',
|
||||
setTrigger: () => 'click',
|
||||
isHover: false,
|
||||
setIsHover: () => false,
|
||||
});
|
||||
|
||||
const Dropdown = ({
|
||||
open = false,
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
onOpenChange,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof DropdownPrimitive.Root>) => {
|
||||
const [trigger, setTrigger] = React.useState<TriggerType>('click');
|
||||
const [isHover, setIsHover] = React.useState(false);
|
||||
return (
|
||||
<DropdownContext.Provider
|
||||
value={{ trigger, setTrigger, isHover, setIsHover }}
|
||||
>
|
||||
<DropdownPrimitive.Root
|
||||
{...props}
|
||||
open={open || isHover}
|
||||
onOpenChange={(open: boolean) => {
|
||||
setIsHover(open);
|
||||
onOpenChange?.(open);
|
||||
}}
|
||||
/>
|
||||
</DropdownContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const DropdownTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.Trigger>,
|
||||
DropdownPrimitive.DropdownMenuTriggerProps &
|
||||
React.ComponentPropsWithoutRef<typeof Button> & {
|
||||
trigger?: TriggerType;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
variant = 'outline',
|
||||
trigger,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { setTrigger, setIsHover } = React.useContext(DropdownContext);
|
||||
|
||||
const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(true);
|
||||
}
|
||||
onMouseEnter?.(e);
|
||||
};
|
||||
|
||||
const handleMouseLeave = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(false);
|
||||
}
|
||||
onMouseLeave?.(e);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (trigger) {
|
||||
setTrigger(trigger);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (props.asChild) {
|
||||
return (
|
||||
<DropdownPrimitive.Trigger
|
||||
className={cn('dropdown-trigger', className)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</DropdownPrimitive.Trigger>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownPrimitive.Trigger asChild>
|
||||
<Button
|
||||
variant={variant}
|
||||
className={cn('dropdown-trigger', className)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
</DropdownPrimitive.Trigger>
|
||||
);
|
||||
},
|
||||
);
|
||||
DropdownTrigger.displayName = DropdownPrimitive.DropdownMenuTrigger.displayName;
|
||||
|
||||
const DropdownGroup = DropdownPrimitive.Group;
|
||||
|
||||
const DropdownPortal = DropdownPrimitive.Portal;
|
||||
|
||||
const DropdownSub = DropdownPrimitive.Sub;
|
||||
|
||||
const DropdownRadioGroup = DropdownPrimitive.RadioGroup;
|
||||
|
||||
const DropdownSubTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownPrimitive.SubTrigger>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
return (
|
||||
<DropdownPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn('dropdown-sub-trigger', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</DropdownPrimitive.SubTrigger>
|
||||
);
|
||||
});
|
||||
DropdownSubTrigger.displayName = DropdownPrimitive.SubTrigger.displayName;
|
||||
|
||||
const DropdownCaption = React.forwardRef<
|
||||
React.ComponentRef<'span'>,
|
||||
React.ComponentPropsWithoutRef<'span'>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<span ref={ref} className={cn('dropdown-caption', className)} {...props} />
|
||||
));
|
||||
DropdownCaption.displayName = 'DropdownCaption';
|
||||
|
||||
const DropdownSubContent = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn('dropdown-sub-content', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DropdownSubContent.displayName = DropdownPrimitive.SubContent.displayName;
|
||||
|
||||
const DropdownContent = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownPrimitive.Content> & {
|
||||
maxHeight?: string;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
sideOffset = 4,
|
||||
maxHeight,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { trigger, setIsHover } = React.useContext(DropdownContext);
|
||||
|
||||
const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(true);
|
||||
}
|
||||
onMouseEnter?.(e);
|
||||
};
|
||||
|
||||
const handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (trigger === 'hover') {
|
||||
setIsHover(false);
|
||||
}
|
||||
onMouseLeave?.(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownPrimitive.Portal>
|
||||
<DropdownPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn('dropdown-content', className)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
{...props}
|
||||
>
|
||||
<ScrollArea maxHeight={maxHeight}>
|
||||
{children}
|
||||
<ScrollBar />
|
||||
</ScrollArea>
|
||||
</DropdownPrimitive.Content>
|
||||
</DropdownPrimitive.Portal>
|
||||
);
|
||||
},
|
||||
);
|
||||
DropdownContent.displayName = DropdownPrimitive.Content.displayName;
|
||||
|
||||
const DropdownItem = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownPrimitive.Item>
|
||||
>(({ children, className, ...props }, ref) => (
|
||||
<DropdownPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn('dropdown-item', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</DropdownPrimitive.Item>
|
||||
));
|
||||
DropdownItem.displayName = DropdownPrimitive.Item.displayName;
|
||||
|
||||
const DropdownCheckboxItem = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn('dropdown-checkbox', className)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<DropdownPrimitive.ItemIndicator className="dropdown-checkbox-icon">
|
||||
<Icon name="RiCheckLine" size={16} />
|
||||
</DropdownPrimitive.ItemIndicator>
|
||||
<Slottable>{children}</Slottable>
|
||||
</DropdownPrimitive.CheckboxItem>
|
||||
));
|
||||
DropdownCheckboxItem.displayName = DropdownPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const DropdownRadioItem = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn('dropdown-radio', className)}
|
||||
{...props}
|
||||
>
|
||||
<DropdownPrimitive.ItemIndicator className="dropdown-radio-icon">
|
||||
<Icon name="RiCircleFill" size={8} className="fill-current" />
|
||||
</DropdownPrimitive.ItemIndicator>
|
||||
<Slottable>{children}</Slottable>
|
||||
</DropdownPrimitive.RadioItem>
|
||||
));
|
||||
DropdownRadioItem.displayName = DropdownPrimitive.RadioItem.displayName;
|
||||
|
||||
const DropdownLabel = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn('dropdown-label', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DropdownLabel.displayName = DropdownPrimitive.Label.displayName;
|
||||
|
||||
const DropdownSeparator = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('dropdown-separator', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DropdownSeparator.displayName = DropdownPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Dropdown,
|
||||
DropdownTrigger,
|
||||
DropdownContent,
|
||||
DropdownCaption,
|
||||
DropdownItem,
|
||||
DropdownCheckboxItem,
|
||||
DropdownRadioItem,
|
||||
DropdownLabel,
|
||||
DropdownSeparator,
|
||||
DropdownGroup,
|
||||
DropdownPortal,
|
||||
DropdownSub,
|
||||
DropdownSubContent,
|
||||
DropdownSubTrigger,
|
||||
DropdownRadioGroup,
|
||||
type DropdownMenuCheckboxItemProps,
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import type * as LabelPrimitive from '@radix-ui/react-label';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
useFormState,
|
||||
} from 'react-hook-form';
|
||||
|
||||
import { Caption } from './caption';
|
||||
import { Label } from './label';
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue,
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState } = useFormContext();
|
||||
const formState = useFormState({ name: fieldContext.name });
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue,
|
||||
);
|
||||
|
||||
function FormItem({ ...props }: React.ComponentProps<'div'>) {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div data-slot="form-item" {...props} />
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function FormLabel({
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
data-slot="form-label"
|
||||
data-error={!!error}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
data-slot="form-control"
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error ?
|
||||
`${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FormCaption({ ...props }: React.ComponentProps<typeof Caption>) {
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<Caption data-slot="form-description" id={formDescriptionId} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function FormMessage({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Caption>) {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error.message ?? '') : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Caption
|
||||
data-slot="form-message"
|
||||
id={formMessageId}
|
||||
variant="error"
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</Caption>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormCaption,
|
||||
FormMessage,
|
||||
FormField,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import * as remixIcons from '@remixicon/react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
const Icons = remixIcons;
|
||||
const IconNames = Object.keys(Icons) as (keyof typeof Icons)[];
|
||||
type IconNameType = keyof typeof Icons;
|
||||
|
||||
interface IconProps extends Omit<React.SVGProps<SVGSVGElement>, 'children'> {
|
||||
name?: IconNameType;
|
||||
color?: string;
|
||||
size?: number | string;
|
||||
}
|
||||
|
||||
const Icon: React.FC<IconProps> = ({
|
||||
name,
|
||||
color = 'currentColor',
|
||||
size = 24,
|
||||
className,
|
||||
onClick,
|
||||
...props
|
||||
}) => {
|
||||
if (!name) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return React.createElement(Icons[name], {
|
||||
color,
|
||||
size,
|
||||
className: cn('icon', onClick && 'icon-clickable', className),
|
||||
onClick,
|
||||
...props,
|
||||
});
|
||||
};
|
||||
|
||||
export { Icon, IconNames, type IconNameType, type IconProps };
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export * from './alert';
|
||||
export * from './accordion';
|
||||
export * from './badge';
|
||||
export * from './button';
|
||||
export * from './checkbox';
|
||||
export * from './calendar';
|
||||
export * from './caption';
|
||||
export * from './combobox';
|
||||
export * from './divider';
|
||||
export * from './dialog';
|
||||
export * from './dropdown';
|
||||
export * from './form';
|
||||
export * from './icon';
|
||||
export * from './input';
|
||||
export * from './label';
|
||||
export * from './menu';
|
||||
export * from './pagination';
|
||||
export * from './popover';
|
||||
export * from './radio-card';
|
||||
export * from './radio';
|
||||
export * from './select';
|
||||
export * from './spinner';
|
||||
export * from './scroll-area';
|
||||
export * from './multi-select';
|
||||
export * from './switch';
|
||||
export * from './sheet';
|
||||
export * from './toast';
|
||||
export * from './tag';
|
||||
export * from './table';
|
||||
export * from './toggle-group';
|
||||
export * from './tooltip';
|
||||
export * from './use-theme';
|
||||
export * from './tabs';
|
||||
export * from './textarea';
|
||||
export * from './timepicker';
|
||||
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import type { ButtonHTMLAttributes, HTMLInputTypeAttribute } from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { ICON_SIZE } from '../constants';
|
||||
import type { Radius, Size } from '../lib/types';
|
||||
import { cn, composeRefs } from '../lib/utils';
|
||||
import type { IconProps } from './icon';
|
||||
import { Icon } from './icon';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
type TextInputType = (
|
||||
| 'text'
|
||||
| 'email'
|
||||
| 'password'
|
||||
| 'search'
|
||||
| 'tel'
|
||||
| 'number'
|
||||
) &
|
||||
HTMLInputTypeAttribute;
|
||||
|
||||
const InputField = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
return <div ref={ref} className={cn('input-field', className)} {...props} />;
|
||||
});
|
||||
InputField.displayName = 'InputField';
|
||||
|
||||
const defaultContext: TextInputProps = {
|
||||
size: undefined,
|
||||
};
|
||||
|
||||
const InputContext = React.createContext<TextInputProps>(defaultContext);
|
||||
|
||||
const inputVariants = cva('input', {
|
||||
variants: {
|
||||
size: {
|
||||
large: 'input-large',
|
||||
medium: 'input-medium',
|
||||
small: 'input-small',
|
||||
},
|
||||
radius: {
|
||||
large: 'input-radius-large',
|
||||
medium: 'input-radius-medium',
|
||||
small: 'input-radius-small',
|
||||
},
|
||||
defaultVariants: {
|
||||
size: undefined,
|
||||
radius: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interface InputBoxProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
size?: Size;
|
||||
}
|
||||
|
||||
const InputBox = React.forwardRef<HTMLInputElement, InputBoxProps>(
|
||||
({ size, className, children, ...props }, ref) => {
|
||||
const { themeSize } = useTheme();
|
||||
|
||||
return (
|
||||
<InputContext.Provider value={{ size: size ?? themeSize }}>
|
||||
<div ref={ref} className={cn('input-box', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
</InputContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
InputBox.displayName = 'InputBox';
|
||||
|
||||
interface TextInputProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
||||
size?: Size;
|
||||
type?: TextInputType;
|
||||
radius?: Radius;
|
||||
}
|
||||
|
||||
const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
size,
|
||||
type = 'text',
|
||||
radius,
|
||||
disabled = false,
|
||||
onFocus,
|
||||
onBlur,
|
||||
className,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const { themeRadius } = useTheme();
|
||||
const { size: boxSize } = React.useContext(InputContext);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
onFocus?.(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
onBlur?.(e);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<input
|
||||
{...rest}
|
||||
spellCheck="false"
|
||||
type={type}
|
||||
ref={composeRefs(inputRef, ref)}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
inputVariants({
|
||||
size: size ?? boxSize,
|
||||
radius: radius ?? themeRadius,
|
||||
className,
|
||||
}),
|
||||
)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
TextInput.displayName = 'TextInput';
|
||||
|
||||
const inputIconVariants = cva('input-icon', {
|
||||
variants: {
|
||||
size: {
|
||||
large: 'input-icon-large',
|
||||
medium: 'input-icon-medium',
|
||||
small: 'input-icon-small',
|
||||
},
|
||||
defaultVariants: {
|
||||
size: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
const InputIcon = ({ className, ...props }: IconProps) => {
|
||||
const { size } = React.useContext(InputContext);
|
||||
const { themeSize } = useTheme();
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
size={ICON_SIZE[size ?? themeSize]}
|
||||
className={cn(inputIconVariants({ size: size ?? themeSize, className }))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
InputIcon.displayName = 'InputIcon';
|
||||
|
||||
const inputButtonVariants = cva('input-button', {
|
||||
variants: {
|
||||
size: {
|
||||
large: 'input-button-large',
|
||||
medium: 'input-button-medium',
|
||||
small: 'input-button-small',
|
||||
},
|
||||
defaultVariants: {
|
||||
size: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
const InputClearButton = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ButtonHTMLAttributes<HTMLButtonElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { size } = React.useContext(InputContext);
|
||||
const { themeSize } = useTheme();
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn(
|
||||
inputButtonVariants({ size: size ?? themeSize, className }),
|
||||
'show-only-on-focus-and-has-value',
|
||||
)}
|
||||
aria-label="Reset input text"
|
||||
{...props}
|
||||
>
|
||||
<Icon name="RiCloseCircleFill" size={ICON_SIZE[size ?? themeSize]} />
|
||||
</button>
|
||||
);
|
||||
});
|
||||
InputClearButton.displayName = 'InputClearButton';
|
||||
|
||||
const InputEyeButton = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
onChangeVisibility?: (visible: boolean) => void;
|
||||
}
|
||||
>(({ className, onClick, onChangeVisibility, ...props }, ref) => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const { size } = React.useContext(InputContext);
|
||||
const { themeSize } = useTheme();
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setVisible((visible) => !visible);
|
||||
onClick?.(e);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
onChangeVisibility?.(visible);
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn(
|
||||
inputButtonVariants({ size: size ?? themeSize, className }),
|
||||
)}
|
||||
onClick={handleClick}
|
||||
aria-label={visible ? 'hide password' : 'show password'}
|
||||
{...props}
|
||||
>
|
||||
{visible ?
|
||||
<Icon name="RiEyeCloseFill" size={ICON_SIZE[size ?? themeSize]} />
|
||||
: <Icon name="RiEyeFill" size={ICON_SIZE[size ?? themeSize]} />}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
InputEyeButton.displayName = 'InputEyeButton';
|
||||
|
||||
export {
|
||||
InputField,
|
||||
InputBox,
|
||||
TextInput,
|
||||
InputClearButton,
|
||||
InputEyeButton,
|
||||
InputIcon,
|
||||
type TextInputProps,
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import * as LabelPrimitive from '@radix-ui/react-label';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface LabelProps extends React.ComponentProps<typeof LabelPrimitive.Root> {}
|
||||
|
||||
function Label({ className, ...props }: LabelProps) {
|
||||
return (
|
||||
<LabelPrimitive.Root
|
||||
data-slot="label"
|
||||
className={cn('label', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Label };
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownContent,
|
||||
DropdownGroup,
|
||||
DropdownItem,
|
||||
DropdownTrigger,
|
||||
} from './dropdown';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const DefaultValue = {
|
||||
orientation: 'horizontal',
|
||||
} as const;
|
||||
|
||||
const menuVariants = cva('menu', {
|
||||
variants: {
|
||||
orientation: {
|
||||
horizontal: 'menu-horizontal',
|
||||
vertical: 'menu-vertical',
|
||||
},
|
||||
defaultVariants: {
|
||||
orientation: DefaultValue.orientation,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const MenuContext = React.createContext<{
|
||||
orientation?: 'vertical' | 'horizontal';
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}>({
|
||||
orientation: DefaultValue.orientation,
|
||||
size: undefined,
|
||||
});
|
||||
|
||||
const Menu = React.forwardRef<
|
||||
React.ComponentRef<typeof ToggleGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> & {
|
||||
orientation?: 'vertical' | 'horizontal';
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
orientation = DefaultValue.orientation,
|
||||
size,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { themeSize } = useTheme();
|
||||
return (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(menuVariants({ orientation, className }))}
|
||||
{...props}
|
||||
>
|
||||
<MenuContext.Provider value={{ orientation, size: size ?? themeSize }}>
|
||||
{children}
|
||||
</MenuContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
Menu.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||
|
||||
const menuItemVariants = cva('menu-item', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'menu-item-small',
|
||||
medium: 'menu-item-medium',
|
||||
large: 'menu-item-large',
|
||||
},
|
||||
defaultVariants: {
|
||||
size: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const MenuItem = React.forwardRef<
|
||||
React.ComponentRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item>
|
||||
>(({ children, className, ...props }, ref) => {
|
||||
const { size } = React.useContext(MenuContext);
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(menuItemVariants({ size, className }))}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
MenuItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||
|
||||
const MenuDropdown = Dropdown;
|
||||
|
||||
const MenuDropdownTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownTrigger>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { size } = React.useContext(MenuContext);
|
||||
|
||||
if (props.asChild) {
|
||||
return (
|
||||
<DropdownTrigger
|
||||
className={cn(menuItemVariants({ size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</DropdownTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownTrigger
|
||||
asChild
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex justify-between',
|
||||
menuItemVariants({ size, className }),
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<button type="button">{children}</button>
|
||||
</DropdownTrigger>
|
||||
);
|
||||
});
|
||||
MenuDropdownTrigger.displayName = 'MenuDropdownTrigger';
|
||||
|
||||
const MenuDropdownContent = DropdownContent;
|
||||
const MenuDropdownGroup = DropdownGroup;
|
||||
const MenuDropdownItem = React.forwardRef<
|
||||
React.ComponentRef<typeof DropdownItem>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuItem>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownItem ref={ref} className={cn('menu-dropdown-item', className)}>
|
||||
<MenuItem {...props} />
|
||||
</DropdownItem>
|
||||
));
|
||||
MenuDropdownItem.displayName = 'MenuDropdownItem';
|
||||
|
||||
export {
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDropdown,
|
||||
MenuDropdownTrigger,
|
||||
MenuDropdownContent,
|
||||
MenuDropdownGroup,
|
||||
MenuDropdownItem,
|
||||
};
|
||||
@@ -0,0 +1,377 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Slot, Slottable } from '@radix-ui/react-slot';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { ICON_SIZE } from '../constants';
|
||||
import { cn } from '../lib/utils';
|
||||
import type { Radius, Size } from '../types';
|
||||
import { Icon } from './icon';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './popover';
|
||||
import { ScrollArea, ScrollBar } from './scroll-area';
|
||||
import { Tag } from './tag';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
type MultiSelectItemType = { value: string; label: React.ReactNode };
|
||||
|
||||
type MultiSelectContextType = {
|
||||
disabled: boolean;
|
||||
size: Size;
|
||||
radius: Radius;
|
||||
selectedItems: MultiSelectItemType[];
|
||||
setSelectedItems: (items: MultiSelectItemType[]) => void;
|
||||
isOpen: boolean;
|
||||
setIsOpen: (open: boolean) => void;
|
||||
toggleOption: (item: MultiSelectItemType) => void;
|
||||
registeredItems: MultiSelectItemType[];
|
||||
registerItem: (item: MultiSelectItemType) => void;
|
||||
};
|
||||
|
||||
const MultiSelectContext = React.createContext<
|
||||
MultiSelectContextType | undefined
|
||||
>(undefined);
|
||||
|
||||
function useMultiSelectContext() {
|
||||
const ctx = React.useContext(MultiSelectContext);
|
||||
if (!ctx)
|
||||
throw new Error('MultiSelect components must be used within <MultiSelect>');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
interface MultiSelectProps {
|
||||
value?: string[];
|
||||
defaultValue?: string[];
|
||||
onValueChange?: (value: string[]) => void;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
size?: Size;
|
||||
radius?: Radius;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const selectVariants = cva('select', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'select-small',
|
||||
medium: 'select-medium',
|
||||
large: 'select-large',
|
||||
},
|
||||
defaultVariants: { size: undefined },
|
||||
},
|
||||
});
|
||||
|
||||
const MultiSelect = React.forwardRef<HTMLDivElement, MultiSelectProps>(
|
||||
function MultiSelect(
|
||||
{
|
||||
value,
|
||||
defaultValue = [],
|
||||
onValueChange,
|
||||
children,
|
||||
className,
|
||||
size,
|
||||
radius,
|
||||
disabled = false,
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const { themeSize, themeRadius } = useTheme();
|
||||
|
||||
const [registeredItems, setRegisteredItems] = React.useState<
|
||||
MultiSelectItemType[]
|
||||
>([]);
|
||||
const [selectedValues, setSelectedValues] =
|
||||
React.useState<string[]>(defaultValue);
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
const values = value ?? selectedValues;
|
||||
const setValues = (vals: string[]) => {
|
||||
if (onValueChange) onValueChange(vals);
|
||||
if (value === undefined) setSelectedValues(vals);
|
||||
};
|
||||
|
||||
const registerItem = React.useCallback((item: MultiSelectItemType) => {
|
||||
setRegisteredItems((prev) =>
|
||||
prev.find((i) => i.value === item.value) ? prev : [...prev, item],
|
||||
);
|
||||
}, []);
|
||||
|
||||
const selectedItems = React.useMemo(
|
||||
() =>
|
||||
values
|
||||
.map((val) => registeredItems.find((item) => item.value === val))
|
||||
.filter(Boolean) as MultiSelectItemType[],
|
||||
[values, registeredItems],
|
||||
);
|
||||
|
||||
const toggleOption = (item: MultiSelectItemType) => {
|
||||
setValues(
|
||||
values.includes(item.value) ?
|
||||
values.filter((v) => v !== item.value)
|
||||
: [...values, item.value],
|
||||
);
|
||||
};
|
||||
|
||||
const hiddenRegisterItems = (
|
||||
<div style={{ display: 'none' }}>
|
||||
{React.Children.map(children, (child) => {
|
||||
if (
|
||||
React.isValidElement(child) &&
|
||||
(child.type as React.ForwardRefExoticComponent<HTMLElement>)
|
||||
.displayName === 'MultiSelectItem'
|
||||
) {
|
||||
return child;
|
||||
}
|
||||
if (
|
||||
React.isValidElement(child) &&
|
||||
(child.props as { children?: React.ReactNode }).children
|
||||
) {
|
||||
return React.Children.map(
|
||||
(child.props as { children?: React.ReactNode }).children,
|
||||
(c) => c,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<MultiSelectContext.Provider
|
||||
value={{
|
||||
disabled,
|
||||
size: size ?? themeSize,
|
||||
radius: radius ?? themeRadius,
|
||||
selectedItems,
|
||||
setSelectedItems: (items) => setValues(items.map((i) => i.value)),
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
toggleOption,
|
||||
registeredItems,
|
||||
registerItem,
|
||||
}}
|
||||
>
|
||||
{hiddenRegisterItems}
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
selectVariants({
|
||||
size: size ?? themeSize,
|
||||
className,
|
||||
}),
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Popover>
|
||||
</MultiSelectContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
interface MultiSelectTriggerProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const selectTriggerVariants = cva('select-trigger', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'select-trigger-small',
|
||||
medium: 'select-trigger-medium',
|
||||
large: 'select-trigger-large',
|
||||
},
|
||||
radius: {
|
||||
small: 'select-trigger-radius-small',
|
||||
medium: 'select-trigger-radius-medium',
|
||||
large: 'select-trigger-radius-large',
|
||||
},
|
||||
},
|
||||
defaultVariants: { size: undefined, radius: undefined },
|
||||
});
|
||||
|
||||
const MultiSelectTrigger = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
MultiSelectTriggerProps
|
||||
>(function MultiSelectTrigger({ asChild, ...props }, ref) {
|
||||
const Comp = asChild ? Slot : MultiSelectTriggerButton;
|
||||
|
||||
return (
|
||||
<PopoverTrigger asChild>
|
||||
<Comp ref={ref} {...props} />
|
||||
</PopoverTrigger>
|
||||
);
|
||||
});
|
||||
|
||||
interface MultiSelectTriggerButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const MultiSelectTriggerButton = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
MultiSelectTriggerButtonProps
|
||||
>(function MultiSelectTrigger({ className, children, ...props }, ref) {
|
||||
const { size, radius, disabled } = useMultiSelectContext();
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
aria-haspopup="listbox"
|
||||
className={cn(selectTriggerVariants({ size, radius, className }))}
|
||||
data-placeholder
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
<Slottable>{children}</Slottable>
|
||||
<Icon name="RiArrowDownSLine" size={ICON_SIZE[size]} />
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
interface MultiSelectContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
maxHeight?: string;
|
||||
}
|
||||
|
||||
const MultiSelectContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
MultiSelectContentProps
|
||||
>(function MultiSelectContent(
|
||||
{ className, children, maxHeight = 'auto', ...props },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<PopoverContent
|
||||
ref={ref}
|
||||
className={cn('select-content', className)}
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('select-viewport')}>
|
||||
<ScrollArea maxHeight={maxHeight}>
|
||||
{children}
|
||||
<ScrollBar />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
);
|
||||
});
|
||||
|
||||
const selectItemVariants = cva('select-item', {
|
||||
variants: { check: { left: 'select-item-left', right: 'select-item-right' } },
|
||||
defaultVariants: { check: 'left' },
|
||||
});
|
||||
|
||||
const selectItemCheckVariants = cva('select-item-check', {
|
||||
variants: {
|
||||
check: { left: 'select-item-check-left', right: 'select-item-check-right' },
|
||||
},
|
||||
defaultVariants: { check: 'left' },
|
||||
});
|
||||
|
||||
interface MultiSelectItemProps {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
const MultiSelectItem = React.forwardRef<HTMLDivElement, MultiSelectItemProps>(
|
||||
function MultiSelectItem({ value, children, className }, ref) {
|
||||
const { selectedItems, toggleOption, registerItem } =
|
||||
useMultiSelectContext();
|
||||
const isSelected = selectedItems.some((item) => item.value === value);
|
||||
|
||||
// Register this item on mount
|
||||
React.useEffect(() => {
|
||||
registerItem({ value, label: children });
|
||||
}, [value, children, registerItem]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="option"
|
||||
aria-selected={isSelected}
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
selectItemVariants({
|
||||
check: typeof children === 'string' ? 'left' : 'right',
|
||||
className,
|
||||
}),
|
||||
)}
|
||||
onClick={() => toggleOption({ value, label: children })}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ')
|
||||
toggleOption({ value, label: children });
|
||||
}}
|
||||
>
|
||||
{isSelected && (
|
||||
<span
|
||||
className={cn(
|
||||
selectItemCheckVariants({
|
||||
check: typeof children === 'string' ? 'left' : 'right',
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<Icon name="RiCheckLine" size={16} />
|
||||
</span>
|
||||
)}
|
||||
<Slottable>{children}</Slottable>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
MultiSelectItem.displayName = 'MultiSelectItem';
|
||||
|
||||
const MultiSelectValue = React.forwardRef<
|
||||
HTMLSpanElement,
|
||||
{ placeholder?: React.ReactNode } & React.HTMLAttributes<HTMLSpanElement>
|
||||
>(function MultiSelectValue({ placeholder, ...props }, ref) {
|
||||
const { selectedItems, size } = useMultiSelectContext();
|
||||
|
||||
if (selectedItems.length === 0) {
|
||||
return (
|
||||
<span ref={ref} {...props}>
|
||||
{placeholder}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span ref={ref} {...props}>
|
||||
{selectedItems.map((item) => (
|
||||
<Tag
|
||||
key={item.value}
|
||||
variant="outline"
|
||||
size={size}
|
||||
className="select-tag"
|
||||
>
|
||||
{item.label}
|
||||
</Tag>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
export {
|
||||
MultiSelect,
|
||||
MultiSelectTrigger,
|
||||
MultiSelectTriggerButton,
|
||||
MultiSelectContent,
|
||||
MultiSelectItem,
|
||||
MultiSelectValue,
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import type { ButtonProps } from './button';
|
||||
import { Button } from './button';
|
||||
import { Icon } from './icon';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const PaginationContext = React.createContext<ButtonProps>({
|
||||
size: undefined,
|
||||
radius: undefined,
|
||||
});
|
||||
|
||||
type PaginationProps = React.ComponentProps<'nav'> &
|
||||
Pick<ButtonProps, 'size' | 'radius'>;
|
||||
const Pagination = ({ size, radius, className, ...props }: PaginationProps) => {
|
||||
const { themeSize, themeRadius } = useTheme();
|
||||
|
||||
return (
|
||||
<PaginationContext.Provider
|
||||
value={{ size: size ?? themeSize, radius: radius ?? themeRadius }}
|
||||
>
|
||||
<nav
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
className={cn('pagination', className)}
|
||||
{...props}
|
||||
/>
|
||||
</PaginationContext.Provider>
|
||||
);
|
||||
};
|
||||
Pagination.displayName = 'Pagination';
|
||||
|
||||
const PaginationContent = React.forwardRef<
|
||||
HTMLUListElement,
|
||||
React.ComponentProps<'ul'>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ul ref={ref} className={cn('pagination-content', className)} {...props} />
|
||||
));
|
||||
PaginationContent.displayName = 'PaginationContent';
|
||||
|
||||
const PaginationItem = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentProps<'li'>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<li ref={ref} className={cn('pagination-item', className)} {...props} />
|
||||
));
|
||||
PaginationItem.displayName = 'PaginationItem';
|
||||
|
||||
type PaginationLinkProps = {
|
||||
isActive?: boolean;
|
||||
} & React.AnchorHTMLAttributes<HTMLAnchorElement>;
|
||||
|
||||
const PaginationLink = ({
|
||||
className,
|
||||
isActive,
|
||||
...props
|
||||
}: PaginationLinkProps) => {
|
||||
const { size, radius } = React.useContext(PaginationContext);
|
||||
return (
|
||||
<Button
|
||||
variant={isActive ? 'outline' : 'ghost'}
|
||||
size={size}
|
||||
radius={radius}
|
||||
className={cn('pagination-link', className)}
|
||||
asChild
|
||||
>
|
||||
<a {...props} aria-current={isActive ? 'page' : undefined} />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
PaginationLink.displayName = 'PaginationLink';
|
||||
|
||||
const PaginationPrevious = ({
|
||||
className,
|
||||
...props
|
||||
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
||||
const { size, radius } = React.useContext(PaginationContext);
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size={size}
|
||||
radius={radius}
|
||||
aria-label="Go to previous page"
|
||||
className={cn('pagination-previous', className)}
|
||||
asChild
|
||||
>
|
||||
<a {...props}>
|
||||
<Icon name="RiArrowLeftSLine" />
|
||||
Previous
|
||||
</a>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
PaginationPrevious.displayName = 'PaginationPrevious';
|
||||
|
||||
const PaginationNext = ({
|
||||
className,
|
||||
...props
|
||||
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
||||
const { size, radius } = React.useContext(PaginationContext);
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size={size}
|
||||
radius={radius}
|
||||
aria-label="Go to next page"
|
||||
className={cn('pagination-next', className)}
|
||||
asChild
|
||||
>
|
||||
<a {...props}>
|
||||
Next
|
||||
<Icon name="RiArrowRightSLine" />
|
||||
</a>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
PaginationNext.displayName = 'PaginationNext';
|
||||
|
||||
const PaginationEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<'button'>) => {
|
||||
const { size, radius } = React.useContext(PaginationContext);
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
variant="ghost"
|
||||
aria-label="More Pages"
|
||||
size={size}
|
||||
radius={radius}
|
||||
className={cn('pagination-ellipsis', className)}
|
||||
>
|
||||
<Icon name="RiMoreLine" />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
PaginationEllipsis.displayName = 'PaginationEllipsis';
|
||||
|
||||
export {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
const Popover = ({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) => {
|
||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
||||
};
|
||||
|
||||
const PopoverTrigger = ({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) => {
|
||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
||||
};
|
||||
|
||||
const PopoverClose = PopoverPrimitive.Close;
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ComponentRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn('popover', className)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
));
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverClose };
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import type { Radius } from '../types';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const radioCardGroupVariants = cva('radio-card-group', {
|
||||
variants: {
|
||||
orientation: {
|
||||
horizontal: 'radio-card-group-horizontal',
|
||||
vertical: 'radio-card-group-vertical',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
orientation: 'horizontal',
|
||||
},
|
||||
});
|
||||
|
||||
const RadioGroupContext = React.createContext<RadioCardGroupProps>({
|
||||
cardType: 'vertical',
|
||||
radius: 'medium',
|
||||
});
|
||||
|
||||
type RadioCardGroupProps = React.ComponentPropsWithoutRef<
|
||||
typeof RadioGroupPrimitive.Root
|
||||
> & {
|
||||
cardType?: 'horizontal' | 'vertical';
|
||||
radius?: Radius;
|
||||
};
|
||||
|
||||
const RadioCardGroup = React.forwardRef<
|
||||
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
||||
RadioCardGroupProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
orientation = 'horizontal',
|
||||
radius,
|
||||
cardType = 'vertical',
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { themeRadius } = useTheme();
|
||||
return (
|
||||
<RadioGroupPrimitive.Root
|
||||
className={cn(radioCardGroupVariants({ orientation, className }))}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<RadioGroupContext.Provider
|
||||
value={{ cardType, radius: radius ?? themeRadius }}
|
||||
>
|
||||
{children}
|
||||
</RadioGroupContext.Provider>
|
||||
</RadioGroupPrimitive.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
RadioCardGroup.displayName = RadioGroupPrimitive.Root.displayName;
|
||||
|
||||
const radioCardVariants = cva('radio-card', {
|
||||
variants: {
|
||||
type: {
|
||||
vertical: 'radio-card-vertical',
|
||||
horizontal: 'radio-card-horizontal',
|
||||
},
|
||||
radius: {
|
||||
small: 'radio-card-radius-small',
|
||||
medium: 'radio-card-radius-medium',
|
||||
large: 'radio-card-radius-large',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
type: 'vertical',
|
||||
radius: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const RadioCard = React.forwardRef<
|
||||
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> &
|
||||
VariantProps<typeof radioCardVariants> & {
|
||||
icon?: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
}
|
||||
>(({ icon, title, description, className, ...props }, ref) => {
|
||||
const { radius, cardType: type } = React.useContext(RadioGroupContext);
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(radioCardVariants({ type, radius, className }))}
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
<span className="radio-card-text">
|
||||
{title && <strong className="radio-card-title">{title}</strong>}
|
||||
{description && (
|
||||
<span className="radio-card-description">{description}</span>
|
||||
)}
|
||||
</span>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
RadioCard.displayName = RadioGroupPrimitive.Item.displayName;
|
||||
|
||||
export { RadioCardGroup, RadioCard };
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
||||
import { Slottable } from '@radix-ui/react-slot';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import { Icon } from './icon';
|
||||
|
||||
const radioGroupVariants = cva('radio-group', {
|
||||
variants: {
|
||||
orientation: {
|
||||
horizontal: 'radio-group-horizontal',
|
||||
vertical: 'radio-group-vertical',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
orientation: 'horizontal',
|
||||
},
|
||||
});
|
||||
|
||||
const RadioGroup = React.forwardRef<
|
||||
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
||||
>(({ orientation, className, ...props }, ref) => {
|
||||
return (
|
||||
<RadioGroupPrimitive.Root
|
||||
className={cn(radioGroupVariants({ orientation, className }))}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
|
||||
|
||||
const RadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
||||
>(({ children, className, ...props }, ref) => {
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn('radio-item', className)}
|
||||
{...props}
|
||||
>
|
||||
<span className="radio">
|
||||
<RadioGroupPrimitive.Indicator className="radio-indicator">
|
||||
<Icon name="RiCircleFill" size={6} />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</span>
|
||||
<Slottable>{children}</Slottable>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
RadioItem.displayName = RadioGroupPrimitive.Item.displayName;
|
||||
|
||||
export { RadioGroup, RadioItem };
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
const scrollBarVariants = cva('scroll-bar', {
|
||||
variants: {
|
||||
orientation: {
|
||||
vertical: 'scroll-bar-vertical',
|
||||
horizontal: 'scroll-bar-horizontal',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
orientation: 'vertical',
|
||||
},
|
||||
});
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & {
|
||||
maxWidth?: string;
|
||||
maxHeight?: string;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
type = 'auto',
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
className,
|
||||
children,
|
||||
style,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
type={type}
|
||||
className={cn('scroll-area', className)}
|
||||
style={{ ...style, maxWidth }}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
className="scroll-area-viewport"
|
||||
style={{ maxHeight }}
|
||||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
),
|
||||
);
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = 'vertical', ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(scrollBarVariants({ orientation, className }))}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="scroll-thumb" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
));
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||
|
||||
export { ScrollArea, ScrollBar };
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { Slottable } from '@radix-ui/react-slot';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { ICON_SIZE } from '../constants';
|
||||
import type { Radius, Size } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Icon } from './icon';
|
||||
import { ScrollArea, ScrollBar } from './scroll-area';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
type SelectContextType = { size: Size; radius: Radius };
|
||||
|
||||
const SelectContext = React.createContext<SelectContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
function useSelectContext() {
|
||||
const ctx = React.useContext(SelectContext);
|
||||
if (!ctx) throw new Error('Select components must be used within <Select>');
|
||||
return ctx;
|
||||
}
|
||||
interface SelectProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root> {
|
||||
className?: string;
|
||||
size?: Size;
|
||||
radius?: Radius;
|
||||
}
|
||||
|
||||
const selectVariants = cva('select', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'select-small',
|
||||
medium: 'select-medium',
|
||||
large: 'select-large',
|
||||
},
|
||||
defaultVariants: { size: undefined },
|
||||
},
|
||||
});
|
||||
|
||||
const Select = ({ size, radius, className, ...props }: SelectProps) => {
|
||||
const { themeSize, themeRadius } = useTheme();
|
||||
return (
|
||||
<SelectContext.Provider
|
||||
value={{ size: size ?? themeSize, radius: radius ?? themeRadius }}
|
||||
>
|
||||
<div
|
||||
className={cn(selectVariants({ size: size ?? themeSize, className }))}
|
||||
>
|
||||
<SelectPrimitive.Root {...props}></SelectPrimitive.Root>
|
||||
</div>
|
||||
</SelectContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
const selectTriggerVariants = cva('select-trigger', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'select-trigger-small',
|
||||
medium: 'select-trigger-medium',
|
||||
large: 'select-trigger-large',
|
||||
},
|
||||
radius: {
|
||||
small: 'select-trigger-radius-small',
|
||||
medium: 'select-trigger-radius-medium',
|
||||
large: 'select-trigger-radius-large',
|
||||
},
|
||||
},
|
||||
defaultVariants: { size: undefined, radius: undefined },
|
||||
});
|
||||
|
||||
interface SelectTriggerProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {}
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Trigger>,
|
||||
SelectTriggerProps
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { size, radius } = useSelectContext();
|
||||
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(selectTriggerVariants({ size, radius, className }))}
|
||||
{...props}
|
||||
>
|
||||
<Slottable>{children}</Slottable>
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<Icon name="RiArrowDownSLine" size={ICON_SIZE[size]} />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
);
|
||||
});
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
interface SelectContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> {
|
||||
maxHeight?: string;
|
||||
}
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Content>,
|
||||
SelectContentProps
|
||||
>(
|
||||
(
|
||||
{ maxHeight = 'auto', className, children, position = 'popper', ...props },
|
||||
ref,
|
||||
) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn('select-content', className)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.Viewport className={cn('select-viewport')}>
|
||||
<ScrollArea maxHeight={maxHeight}>
|
||||
{children}
|
||||
<ScrollBar />
|
||||
</ScrollArea>
|
||||
</SelectPrimitive.Viewport>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
),
|
||||
);
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
const SelectGroupLabel = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn('select-group-label', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectGroupLabel.displayName = 'SelectGroupLabel';
|
||||
|
||||
const selectItemVariants = cva('select-item', {
|
||||
variants: { check: { left: 'select-item-left', right: 'select-item-right' } },
|
||||
defaultVariants: { check: 'left' },
|
||||
});
|
||||
|
||||
const selectItemCheckVariants = cva('select-item-check', {
|
||||
variants: {
|
||||
check: { left: 'select-item-check-left', right: 'select-item-check-right' },
|
||||
},
|
||||
defaultVariants: { check: 'left' },
|
||||
});
|
||||
|
||||
type SelectItemProps = React.ComponentPropsWithoutRef<
|
||||
typeof SelectPrimitive.Item
|
||||
>;
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Item>,
|
||||
SelectItemProps
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
selectItemVariants({
|
||||
check: typeof children === 'string' ? 'left' : 'right',
|
||||
className,
|
||||
}),
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.ItemIndicator
|
||||
className={cn(
|
||||
selectItemCheckVariants({
|
||||
check: typeof children === 'string' ? 'left' : 'right',
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<Icon name="RiCheckLine" size={16} />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('select-separator', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectGroupLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SheetPrimitive from '@radix-ui/react-dialog';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import { Button } from './button';
|
||||
import type { IconNameType } from './icon';
|
||||
import { Icon } from './icon';
|
||||
import { ScrollArea, ScrollBar } from './scroll-area';
|
||||
|
||||
const Sheet = SheetPrimitive.Root;
|
||||
|
||||
const SheetTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Trigger>,
|
||||
SheetPrimitive.DialogTriggerProps &
|
||||
React.ComponentPropsWithoutRef<typeof Button>
|
||||
>(({ variant = 'outline', className, children, ...props }, ref) => {
|
||||
if (props.asChild) {
|
||||
return (
|
||||
<SheetPrimitive.Trigger
|
||||
className={cn('sheet-trigger', className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</SheetPrimitive.Trigger>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SheetPrimitive.Trigger asChild>
|
||||
<Button
|
||||
variant={variant}
|
||||
className={cn('sheet-trigger', className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
</SheetPrimitive.Trigger>
|
||||
);
|
||||
});
|
||||
SheetTrigger.displayName = SheetPrimitive.Trigger.displayName;
|
||||
|
||||
const SheetClose = React.forwardRef<
|
||||
React.ElementRef<typeof Button>,
|
||||
React.ComponentPropsWithoutRef<typeof Button>
|
||||
>(({ variant, ...props }, ref) => (
|
||||
<SheetPrimitive.Close asChild>
|
||||
<Button ref={ref} variant={variant ?? 'outline'} {...props} />
|
||||
</SheetPrimitive.Close>
|
||||
));
|
||||
SheetClose.displayName = 'SheetClose';
|
||||
|
||||
const SheetPortal = SheetPrimitive.Portal;
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn('sheet-overlay', className)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
));
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||
|
||||
const sheetVariants = cva('sheet', {
|
||||
variants: {
|
||||
side: {
|
||||
top: 'sheet-side-top',
|
||||
bottom: 'sheet-side-bottom',
|
||||
left: 'sheet-side-left',
|
||||
right: 'sheet-side-right',
|
||||
},
|
||||
radius: {
|
||||
small: 'sheet-radius-small',
|
||||
medium: 'sheet-radius-medium',
|
||||
large: 'sheet-radius-large',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: 'right',
|
||||
radius: 'small',
|
||||
},
|
||||
});
|
||||
|
||||
interface SheetContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
SheetContentProps
|
||||
>(
|
||||
(
|
||||
{ side = 'right', radius = 'small', className, children, ...props },
|
||||
ref,
|
||||
) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay>
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side, radius }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close asChild>
|
||||
<Button
|
||||
size="medium"
|
||||
variant="ghost"
|
||||
className="sheet-close"
|
||||
aria-label="Close"
|
||||
>
|
||||
<Icon name="RiCloseLine" />
|
||||
</Button>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetOverlay>
|
||||
</SheetPortal>
|
||||
),
|
||||
);
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
||||
|
||||
interface SheetHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
icon?: IconNameType;
|
||||
}
|
||||
const SheetHeader = ({
|
||||
icon,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: SheetHeaderProps) => (
|
||||
<div className={cn('sheet-header', className)} {...props}>
|
||||
{icon && <Icon name={icon} size={38} className="sheet-icon" />}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
SheetHeader.displayName = 'SheetHeader';
|
||||
|
||||
interface SheetBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
const SheetBody = ({ asChild, className, ...props }: SheetBodyProps) => {
|
||||
const Comp = asChild ? Slot : 'div';
|
||||
return (
|
||||
<ScrollArea>
|
||||
<Comp className={cn(className)} {...props} />
|
||||
<ScrollBar />
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
SheetBody.displayName = 'SheetBody';
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn('sheet-footer', className)} {...props} />
|
||||
);
|
||||
SheetFooter.displayName = 'SheetFooter';
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('sheet-title', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName;
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('sheet-description', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetPortal,
|
||||
SheetOverlay,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetBody,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import type { Size } from '../types';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
export interface SpinnerProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
size?: Size;
|
||||
}
|
||||
|
||||
const defaultVariants: {
|
||||
size?: Size;
|
||||
} = {
|
||||
size: undefined,
|
||||
};
|
||||
|
||||
const spinnerVariants = cva('spinner', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'spinner-small',
|
||||
medium: 'spinner-medium',
|
||||
large: 'spinner-large',
|
||||
},
|
||||
},
|
||||
defaultVariants,
|
||||
});
|
||||
|
||||
const Spinner: React.FC<SpinnerProps> = (props) => {
|
||||
const { size, className } = props;
|
||||
const { themeSize } = useTheme();
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cn(spinnerVariants({ size: size ?? themeSize }), className)}
|
||||
aria-label="loading..."
|
||||
></span>
|
||||
);
|
||||
};
|
||||
export { Spinner };
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import type { Color } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
const switchVariants = cva('switch', {
|
||||
variants: {
|
||||
color: {
|
||||
default: 'switch-default',
|
||||
blue: 'switch-blue',
|
||||
orange: 'switch-orange',
|
||||
red: 'switch-red',
|
||||
green: 'switch-green',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
interface SwitchProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {
|
||||
color?: Color;
|
||||
}
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||
SwitchProps
|
||||
>(({ color, className, children, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(switchVariants({ color }), className)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb className={cn('switch-thumb')} />
|
||||
{children}
|
||||
</SwitchPrimitives.Root>
|
||||
));
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||
|
||||
export { Switch };
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import type { IconNameType } from './icon';
|
||||
import { Icon } from './icon';
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<table ref={ref} className={cn('table', className)} {...props} />
|
||||
));
|
||||
Table.displayName = 'Table';
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn('table-header', className)} {...props} />
|
||||
));
|
||||
TableHeader.displayName = 'TableHeader';
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody ref={ref} className={cn('table-body', className)} {...props} />
|
||||
));
|
||||
TableBody.displayName = 'TableBody';
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot ref={ref} className={cn('table-footer', className)} {...props} />
|
||||
));
|
||||
TableFooter.displayName = 'TableFooter';
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr ref={ref} className={cn('table-row', className)} {...props} />
|
||||
));
|
||||
TableRow.displayName = 'TableRow';
|
||||
|
||||
const tableHeadVariants = cva('table-head', {
|
||||
variants: {
|
||||
textAlign: {
|
||||
left: 'table-head-left',
|
||||
center: 'table-head-center',
|
||||
right: 'table-head-right',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
textAlign: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement> &
|
||||
VariantProps<typeof tableHeadVariants> & {
|
||||
icon?: IconNameType;
|
||||
}
|
||||
>(({ icon, textAlign = 'left', children, className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(tableHeadVariants({ textAlign, className }))}
|
||||
{...props}
|
||||
>
|
||||
{icon && <Icon name={icon} size={16} />}
|
||||
{children}
|
||||
</th>
|
||||
));
|
||||
TableHead.displayName = 'TableHead';
|
||||
|
||||
const tableCellVariants = cva('table-cell', {
|
||||
variants: {
|
||||
textAlign: {
|
||||
left: 'table-cell-left',
|
||||
center: 'table-cell-center',
|
||||
right: 'table-cell-right',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
textAlign: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement> &
|
||||
VariantProps<typeof tableCellVariants>
|
||||
>(({ textAlign = 'left', className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn(tableCellVariants({ textAlign, className }))}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCell.displayName = 'TableCell';
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption ref={ref} className={cn('table-caption', className)} {...props} />
|
||||
));
|
||||
TableCaption.displayName = 'TableCaption';
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
const Tabs = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Root ref={ref} className={cn('tabs', className)} {...props} />
|
||||
));
|
||||
|
||||
Tabs.displayName = TabsPrimitive.Root.displayName;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn('tabs-list', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn('tabs-trigger', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn('tabs-content', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import type { Radius, Size } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
type TagVariant = 'primary' | 'secondary' | 'outline' | 'destructive';
|
||||
|
||||
const tagVariants = cva('tag', {
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'tag-primary',
|
||||
secondary: 'tag-secondary',
|
||||
outline: 'tag-outline',
|
||||
destructive: 'tag-destructive',
|
||||
},
|
||||
size: {
|
||||
large: 'tag-large',
|
||||
medium: 'tag-medium',
|
||||
small: 'tag-small',
|
||||
},
|
||||
radius: {
|
||||
large: 'tag-radius-large',
|
||||
medium: 'tag-radius-medium',
|
||||
small: 'tag-radius-small',
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'primary',
|
||||
size: undefined,
|
||||
radius: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interface TagProps extends React.HTMLAttributes<HTMLElement> {
|
||||
variant?: TagVariant;
|
||||
size?: Size;
|
||||
radius?: Radius;
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Tag = React.forwardRef<HTMLElement, TagProps>((props, ref) => {
|
||||
const {
|
||||
variant = 'primary',
|
||||
size,
|
||||
radius,
|
||||
className,
|
||||
children,
|
||||
asChild,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const { themeSize, themeRadius } = useTheme();
|
||||
const Comp = asChild ? Slot : 'span';
|
||||
|
||||
return (
|
||||
<Comp
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
tagVariants({
|
||||
variant,
|
||||
size: size ?? themeSize,
|
||||
radius: radius ?? themeRadius,
|
||||
className,
|
||||
}),
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Comp>
|
||||
);
|
||||
});
|
||||
|
||||
Tag.displayName = 'Tag';
|
||||
|
||||
export { Tag, type TagProps };
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
const Textarea = React.forwardRef<
|
||||
HTMLTextAreaElement,
|
||||
React.ComponentProps<'textarea'>
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea className={cn('textarea', className)} ref={ref} {...props} />
|
||||
);
|
||||
});
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
export { Textarea };
|
||||
@@ -0,0 +1,533 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import { Icon } from './icon';
|
||||
import type { TextInputProps } from './input';
|
||||
import { InputBox, InputField, TextInput } from './input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from './select';
|
||||
|
||||
interface PeriodSelectorProps {
|
||||
period: Period;
|
||||
setPeriod?: (m: Period) => void;
|
||||
date?: Date | null;
|
||||
onDateChange?: (date: Date | undefined) => void;
|
||||
onRightFocus?: () => void;
|
||||
onLeftFocus?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TimePeriodSelect = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
PeriodSelectorProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
period,
|
||||
setPeriod,
|
||||
date,
|
||||
onDateChange,
|
||||
onLeftFocus,
|
||||
onRightFocus,
|
||||
className,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
if (e.key === 'ArrowRight') onRightFocus?.();
|
||||
if (e.key === 'ArrowLeft') onLeftFocus?.();
|
||||
};
|
||||
|
||||
const handleValueChange = (value: Period) => {
|
||||
setPeriod?.(value);
|
||||
|
||||
/**
|
||||
* trigger an update whenever the user switches between AM and PM;
|
||||
* otherwise user must manually change the hour each time
|
||||
*/
|
||||
if (date) {
|
||||
const tempDate = new Date(date);
|
||||
const hours = display12HourValue(date.getHours());
|
||||
onDateChange?.(
|
||||
setDateByType(
|
||||
tempDate,
|
||||
hours.toString(),
|
||||
'12hours',
|
||||
period === 'AM' ? 'PM' : 'AM',
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={period}
|
||||
onValueChange={(value: Period) => handleValueChange(value)}
|
||||
>
|
||||
<SelectTrigger
|
||||
className={className}
|
||||
ref={ref}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="AM">AM</SelectItem>
|
||||
<SelectItem value="PM">PM</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
TimePeriodSelect.displayName = 'TimePeriodSelect';
|
||||
|
||||
interface TimePickerInputProps extends React.PropsWithoutRef<TextInputProps> {
|
||||
picker: TimePickerType;
|
||||
date?: Date | null;
|
||||
onDateChange?: (date: Date | undefined) => void;
|
||||
period?: Period;
|
||||
onRightFocus?: () => void;
|
||||
onLeftFocus?: () => void;
|
||||
}
|
||||
|
||||
const TimePickerInput = React.forwardRef<
|
||||
HTMLInputElement,
|
||||
TimePickerInputProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
type = 'tel',
|
||||
value,
|
||||
id,
|
||||
name,
|
||||
date = new Date(new Date().setHours(0, 0, 0, 0)),
|
||||
onDateChange,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
picker,
|
||||
period,
|
||||
onLeftFocus,
|
||||
onRightFocus,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [flag, setFlag] = React.useState<boolean>(false);
|
||||
const [prevIntKey, setPrevIntKey] = React.useState<string>('0');
|
||||
|
||||
/**
|
||||
* allow the user to enter the second digit within 2 seconds
|
||||
* otherwise start again with entering first digit
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
if (flag) {
|
||||
const timer = setTimeout(() => {
|
||||
setFlag(false);
|
||||
}, 2000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [flag]);
|
||||
|
||||
const calculatedValue = React.useMemo(() => {
|
||||
return getDateByType(date, picker);
|
||||
}, [date, picker]);
|
||||
|
||||
const calculateNewValue = (key: string) => {
|
||||
/*
|
||||
* If picker is '12hours' and the first digit is 0, then the second digit is automatically set to 1.
|
||||
* The second entered digit will break the condition and the value will be set to 10-12.
|
||||
*/
|
||||
if (picker === '12hours') {
|
||||
if (flag && calculatedValue.slice(1, 2) === '1' && prevIntKey === '0')
|
||||
return `0${key}`;
|
||||
}
|
||||
|
||||
return !flag ? `0${key}` : calculatedValue.slice(1, 2) + key;
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Tab') return;
|
||||
e.preventDefault();
|
||||
if (e.key === 'ArrowRight') onRightFocus?.();
|
||||
if (e.key === 'ArrowLeft') onLeftFocus?.();
|
||||
if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
|
||||
const step = e.key === 'ArrowUp' ? 1 : -1;
|
||||
const newValue = getArrowByType(calculatedValue, step, picker);
|
||||
if (flag) setFlag(false);
|
||||
const tempDate = date ? new Date(date) : new Date();
|
||||
onDateChange?.(setDateByType(tempDate, newValue, picker, period));
|
||||
}
|
||||
if (e.key >= '0' && e.key <= '9') {
|
||||
if (picker === '12hours') setPrevIntKey(e.key);
|
||||
|
||||
const newValue = calculateNewValue(e.key);
|
||||
if (flag) onRightFocus?.();
|
||||
setFlag((prev) => !prev);
|
||||
const tempDate = date ? new Date(date) : new Date();
|
||||
onDateChange?.(setDateByType(tempDate, newValue, picker, period));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<InputField className={cn('w-24', className)}>
|
||||
<InputBox>
|
||||
<TextInput
|
||||
ref={ref}
|
||||
id={id ?? picker}
|
||||
name={name ?? picker}
|
||||
className="text-left font-mono tabular-nums caret-transparent [&::-webkit-inner-spin-button]:appearance-none"
|
||||
value={value ?? calculatedValue}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
onChange?.(e);
|
||||
}}
|
||||
type={type}
|
||||
inputMode="decimal"
|
||||
onKeyDown={(e) => {
|
||||
onKeyDown?.(e);
|
||||
handleKeyDown(e);
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</InputBox>
|
||||
</InputField>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
TimePickerInput.displayName = 'TimePickerInput';
|
||||
|
||||
interface TimePickerProps {
|
||||
date?: Date | null;
|
||||
onChange?: (date: Date | undefined) => void;
|
||||
hourCycle?: 12 | 24;
|
||||
/**
|
||||
* Determines the smallest unit that is displayed in the datetime picker.
|
||||
* Default is 'second'.
|
||||
* */
|
||||
granularity?: Granularity;
|
||||
}
|
||||
|
||||
interface TimePickerRef {
|
||||
minuteRef: HTMLInputElement | null;
|
||||
hourRef: HTMLInputElement | null;
|
||||
secondRef: HTMLInputElement | null;
|
||||
}
|
||||
|
||||
const TimePicker = React.forwardRef<TimePickerRef, TimePickerProps>(
|
||||
({ date, onChange, hourCycle = 24, granularity = 'second' }, ref) => {
|
||||
const minuteRef = React.useRef<HTMLInputElement>(null);
|
||||
const hourRef = React.useRef<HTMLInputElement>(null);
|
||||
const secondRef = React.useRef<HTMLInputElement>(null);
|
||||
const periodRef = React.useRef<HTMLButtonElement>(null);
|
||||
const [period, setPeriod] = React.useState<Period>(
|
||||
date && date.getHours() >= 12 ? 'PM' : 'AM',
|
||||
);
|
||||
|
||||
React.useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
minuteRef: minuteRef.current,
|
||||
hourRef: hourRef.current,
|
||||
secondRef: secondRef.current,
|
||||
periodRef: periodRef.current,
|
||||
}),
|
||||
[minuteRef, hourRef, secondRef],
|
||||
);
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<label
|
||||
htmlFor="datetime-picker-hour-input"
|
||||
className="flex cursor-pointer items-center"
|
||||
>
|
||||
<Icon name="RiTimeLine" size={16} />
|
||||
</label>
|
||||
<TimePickerInput
|
||||
picker={hourCycle === 24 ? 'hours' : '12hours'}
|
||||
date={date}
|
||||
id="datetime-picker-hour-input"
|
||||
onDateChange={onChange}
|
||||
ref={hourRef}
|
||||
period={period}
|
||||
onRightFocus={() => minuteRef.current?.focus()}
|
||||
/>
|
||||
{(granularity === 'minute' || granularity === 'second') && (
|
||||
<>
|
||||
:
|
||||
<TimePickerInput
|
||||
picker="minutes"
|
||||
date={date}
|
||||
onDateChange={onChange}
|
||||
ref={minuteRef}
|
||||
onLeftFocus={() => hourRef.current?.focus()}
|
||||
onRightFocus={() => secondRef.current?.focus()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{granularity === 'second' && (
|
||||
<>
|
||||
:
|
||||
<TimePickerInput
|
||||
picker="seconds"
|
||||
date={date}
|
||||
onDateChange={onChange}
|
||||
ref={secondRef}
|
||||
onLeftFocus={() => minuteRef.current?.focus()}
|
||||
onRightFocus={() => periodRef.current?.focus()}
|
||||
className="w-24"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{hourCycle === 12 && (
|
||||
<TimePeriodSelect
|
||||
period={period}
|
||||
setPeriod={setPeriod}
|
||||
date={date}
|
||||
onDateChange={(date) => {
|
||||
onChange?.(date);
|
||||
if (date && date.getHours() >= 12) {
|
||||
setPeriod('PM');
|
||||
} else {
|
||||
setPeriod('AM');
|
||||
}
|
||||
}}
|
||||
ref={periodRef}
|
||||
onLeftFocus={() => secondRef.current?.focus()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
TimePicker.displayName = 'TimePicker';
|
||||
|
||||
type Granularity = 'hour' | 'minute' | 'second';
|
||||
|
||||
// ---------- utils start ----------
|
||||
/**
|
||||
* regular expression to check for valid hour format (01-23)
|
||||
*/
|
||||
function isValidHour(value: string) {
|
||||
return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* regular expression to check for valid 12 hour format (01-12)
|
||||
*/
|
||||
function isValid12Hour(value: string) {
|
||||
return /^(0[1-9]|1[0-2])$/.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* regular expression to check for valid minute format (00-59)
|
||||
*/
|
||||
function isValidMinuteOrSecond(value: string) {
|
||||
return /^[0-5][0-9]$/.test(value);
|
||||
}
|
||||
|
||||
type GetValidNumberConfig = { max: number; min?: number; loop?: boolean };
|
||||
|
||||
function getValidNumber(
|
||||
value: string,
|
||||
{ max, min = 0, loop = false }: GetValidNumberConfig,
|
||||
) {
|
||||
let numericValue = parseInt(value, 10);
|
||||
|
||||
if (!Number.isNaN(numericValue)) {
|
||||
if (!loop) {
|
||||
if (numericValue > max) numericValue = max;
|
||||
if (numericValue < min) numericValue = min;
|
||||
} else {
|
||||
if (numericValue > max) numericValue = min;
|
||||
if (numericValue < min) numericValue = max;
|
||||
}
|
||||
return numericValue.toString().padStart(2, '0');
|
||||
}
|
||||
|
||||
return '00';
|
||||
}
|
||||
|
||||
function getValidHour(value: string) {
|
||||
if (isValidHour(value)) return value;
|
||||
return getValidNumber(value, { max: 23 });
|
||||
}
|
||||
|
||||
function getValid12Hour(value: string) {
|
||||
if (isValid12Hour(value)) return value;
|
||||
return getValidNumber(value, { min: 1, max: 12 });
|
||||
}
|
||||
|
||||
function getValidMinuteOrSecond(value: string) {
|
||||
if (isValidMinuteOrSecond(value)) return value;
|
||||
return getValidNumber(value, { max: 59 });
|
||||
}
|
||||
|
||||
type GetValidArrowNumberConfig = {
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
};
|
||||
|
||||
function getValidArrowNumber(
|
||||
value: string,
|
||||
{ min, max, step }: GetValidArrowNumberConfig,
|
||||
) {
|
||||
let numericValue = parseInt(value, 10);
|
||||
if (!Number.isNaN(numericValue)) {
|
||||
numericValue += step;
|
||||
return getValidNumber(String(numericValue), { min, max, loop: true });
|
||||
}
|
||||
return '00';
|
||||
}
|
||||
|
||||
function getValidArrowHour(value: string, step: number) {
|
||||
return getValidArrowNumber(value, { min: 0, max: 23, step });
|
||||
}
|
||||
|
||||
function getValidArrow12Hour(value: string, step: number) {
|
||||
return getValidArrowNumber(value, { min: 1, max: 12, step });
|
||||
}
|
||||
|
||||
function getValidArrowMinuteOrSecond(value: string, step: number) {
|
||||
return getValidArrowNumber(value, { min: 0, max: 59, step });
|
||||
}
|
||||
|
||||
function setMinutes(date: Date, value: string) {
|
||||
const minutes = getValidMinuteOrSecond(value);
|
||||
date.setMinutes(parseInt(minutes, 10));
|
||||
return date;
|
||||
}
|
||||
|
||||
function setSeconds(date: Date, value: string) {
|
||||
const seconds = getValidMinuteOrSecond(value);
|
||||
date.setSeconds(parseInt(seconds, 10));
|
||||
return date;
|
||||
}
|
||||
|
||||
function setHours(date: Date, value: string) {
|
||||
const hours = getValidHour(value);
|
||||
date.setHours(parseInt(hours, 10));
|
||||
return date;
|
||||
}
|
||||
|
||||
function set12Hours(date: Date, value: string, period: Period) {
|
||||
const hours = parseInt(getValid12Hour(value), 10);
|
||||
const convertedHours = convert12HourTo24Hour(hours, period);
|
||||
date.setHours(convertedHours);
|
||||
return date;
|
||||
}
|
||||
|
||||
type TimePickerType = 'minutes' | 'seconds' | 'hours' | '12hours';
|
||||
export type Period = 'AM' | 'PM';
|
||||
|
||||
function setDateByType(
|
||||
date: Date,
|
||||
value: string,
|
||||
type: TimePickerType,
|
||||
period?: Period,
|
||||
) {
|
||||
switch (type) {
|
||||
case 'minutes':
|
||||
return setMinutes(date, value);
|
||||
case 'seconds':
|
||||
return setSeconds(date, value);
|
||||
case 'hours':
|
||||
return setHours(date, value);
|
||||
case '12hours': {
|
||||
if (!period) return date;
|
||||
return set12Hours(date, value, period);
|
||||
}
|
||||
default:
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
function getDateByType(date: Date | null, type: TimePickerType) {
|
||||
if (!date) return '00';
|
||||
switch (type) {
|
||||
case 'minutes':
|
||||
return getValidMinuteOrSecond(String(date.getMinutes()));
|
||||
case 'seconds':
|
||||
return getValidMinuteOrSecond(String(date.getSeconds()));
|
||||
case 'hours':
|
||||
return getValidHour(String(date.getHours()));
|
||||
case '12hours':
|
||||
return getValid12Hour(String(display12HourValue(date.getHours())));
|
||||
default:
|
||||
return '00';
|
||||
}
|
||||
}
|
||||
|
||||
function getArrowByType(value: string, step: number, type: TimePickerType) {
|
||||
switch (type) {
|
||||
case 'minutes':
|
||||
return getValidArrowMinuteOrSecond(value, step);
|
||||
case 'seconds':
|
||||
return getValidArrowMinuteOrSecond(value, step);
|
||||
case 'hours':
|
||||
return getValidArrowHour(value, step);
|
||||
case '12hours':
|
||||
return getValidArrow12Hour(value, step);
|
||||
default:
|
||||
return '00';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles value change of 12-hour input
|
||||
* 12:00 PM is 12:00
|
||||
* 12:00 AM is 00:00
|
||||
*/
|
||||
function convert12HourTo24Hour(hour: number, period: Period) {
|
||||
if (period === 'PM') {
|
||||
if (hour <= 11) {
|
||||
return hour + 12;
|
||||
}
|
||||
return hour;
|
||||
}
|
||||
|
||||
if (hour === 12) return 0;
|
||||
return hour;
|
||||
}
|
||||
|
||||
/**
|
||||
* time is stored in the 24-hour form,
|
||||
* but needs to be displayed to the user
|
||||
* in its 12-hour representation
|
||||
*/
|
||||
function display12HourValue(hours: number) {
|
||||
if (hours === 0 || hours === 12) return '12';
|
||||
if (hours >= 22) return `${hours - 12}`;
|
||||
if (hours % 12 > 9) return `${hours}`;
|
||||
return `0${hours % 12}`;
|
||||
}
|
||||
|
||||
// ---------- utils end ----------
|
||||
|
||||
export { TimePickerInput, TimePeriodSelect };
|
||||
export type { TimePickerInputProps, PeriodSelectorProps };
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Toaster as Sonner, toast } from 'sonner';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import { Icon } from './icon';
|
||||
import { Spinner } from './spinner';
|
||||
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||
|
||||
const Toaster = ({
|
||||
className,
|
||||
toastOptions,
|
||||
icons,
|
||||
...props
|
||||
}: ToasterProps) => {
|
||||
return (
|
||||
<Sonner
|
||||
className={cn('toaster', className)}
|
||||
toastOptions={{
|
||||
unstyled: true,
|
||||
classNames: {
|
||||
toast: cn('toast', toastOptions?.classNames?.toast ?? ''),
|
||||
title: cn('toast-title', toastOptions?.classNames?.title ?? ''),
|
||||
description: cn(
|
||||
'toast-description',
|
||||
toastOptions?.classNames?.description ?? '',
|
||||
),
|
||||
loader: cn('toast-loader', toastOptions?.classNames?.loader ?? ''),
|
||||
cancelButton: cn(
|
||||
'toast-close button button-ghost button-medium button-radius-medium',
|
||||
toastOptions?.classNames?.cancelButton ?? '',
|
||||
),
|
||||
actionButton: cn(
|
||||
'toast-button button button-outline button-medium button-radius-medium',
|
||||
toastOptions?.classNames?.actionButton ?? '',
|
||||
),
|
||||
success: cn('toast-success', toastOptions?.classNames?.success ?? ''),
|
||||
error: cn('toast-error', toastOptions?.classNames?.error ?? ''),
|
||||
info: cn('toast-info', toastOptions?.classNames?.info ?? ''),
|
||||
warning: cn('toast-warning', toastOptions?.classNames?.warning ?? ''),
|
||||
loading: '',
|
||||
default: cn('toast-default', toastOptions?.classNames?.default ?? ''),
|
||||
content: cn('toast-content', toastOptions?.classNames?.content ?? ''),
|
||||
icon: cn('toast-icon', toastOptions?.classNames?.icon ?? ''),
|
||||
},
|
||||
}}
|
||||
icons={{
|
||||
...icons,
|
||||
loading: icons?.loading ?? <Spinner size="small" />,
|
||||
warning: icons?.warning ?? (
|
||||
<Icon
|
||||
name="RiErrorWarningFill"
|
||||
size={20}
|
||||
className="toast-icon-warning"
|
||||
/>
|
||||
),
|
||||
success: icons?.success ?? (
|
||||
<Icon
|
||||
name="RiCheckboxCircleFill"
|
||||
size={20}
|
||||
className="toast-icon-success"
|
||||
/>
|
||||
),
|
||||
error: icons?.success ?? (
|
||||
<Icon
|
||||
name="RiCloseCircleFill"
|
||||
size={20}
|
||||
className="toast-icon-error"
|
||||
/>
|
||||
),
|
||||
info: icons?.info ?? (
|
||||
<Icon
|
||||
name="RiInformation2Fill"
|
||||
size={20}
|
||||
className="toast-icon-informative"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Toaster, toast };
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import type * as TogglePrimitive from '@radix-ui/react-toggle';
|
||||
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import type { Radius, Size } from '../lib/types';
|
||||
import { cn } from '../lib/utils';
|
||||
import useTheme from './use-theme';
|
||||
|
||||
const toggleVariants = cva('toggle-group-item', {
|
||||
variants: {
|
||||
size: {
|
||||
small: 'toggle-group-item-small',
|
||||
medium: 'toggle-group-item-medium',
|
||||
large: 'toggle-group-item-large',
|
||||
},
|
||||
radius: {
|
||||
small: 'toggle-group-item-radius-small',
|
||||
medium: 'toggle-group-item-radius-medium',
|
||||
large: 'toggle-group-item-radius-large',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: undefined,
|
||||
radius: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const ToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants>
|
||||
>({
|
||||
size: undefined,
|
||||
radius: undefined,
|
||||
});
|
||||
|
||||
const ToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, size, radius, children, ...props }, ref) => {
|
||||
const { themeSize, themeRadius } = useTheme();
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn('toggle-group', className)}
|
||||
{...props}
|
||||
>
|
||||
<ToggleGroupContext.Provider
|
||||
value={{ size: size ?? themeSize, radius: radius ?? themeRadius }}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
);
|
||||
});
|
||||
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||
|
||||
interface ToggleGroupItemProps
|
||||
extends React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root>,
|
||||
Omit<VariantProps<typeof toggleVariants>, 'disabled'> {
|
||||
size?: Size;
|
||||
radius?: Radius;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const ToggleGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
ToggleGroupItemProps
|
||||
>(({ className, children, size, radius, disabled, value, ...props }, ref) => {
|
||||
const context = React.useContext(ToggleGroupContext);
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
size: size ?? context.size,
|
||||
radius: radius ?? context.radius,
|
||||
}),
|
||||
className,
|
||||
)}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { Slottable } from '@radix-ui/react-slot';
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
const tooltipVariants = cva('tooltip', {
|
||||
variants: {
|
||||
textAlign: {
|
||||
left: 'tooltip-text-left',
|
||||
center: 'tooltip-text-center',
|
||||
right: 'tooltip-text-right',
|
||||
},
|
||||
defaultVariants: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root;
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
interface TooltipContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> {
|
||||
textAlign?: 'left' | 'center' | 'right';
|
||||
}
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
TooltipContentProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
side,
|
||||
sideOffset = 4,
|
||||
textAlign = 'center',
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
side={side}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(tooltipVariants({ textAlign, className }))}
|
||||
{...props}
|
||||
>
|
||||
{title && <strong className={cn('tooltip-title')}>{title}</strong>}
|
||||
<Slottable>{children}</Slottable>
|
||||
{side !== undefined && (
|
||||
<>
|
||||
<TooltipPrimitive.TooltipArrow
|
||||
width={10}
|
||||
height={6}
|
||||
className={cn('tooltip-arrow-border')}
|
||||
/>
|
||||
<TooltipPrimitive.TooltipArrow
|
||||
width={8}
|
||||
height={5}
|
||||
className={cn('tooltip-arrow')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TooltipPrimitive.Content>
|
||||
),
|
||||
);
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
type TooltipContentProps,
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import type { Radius, Size } from '../types';
|
||||
|
||||
const DefaultValue: {
|
||||
themeSize: Size;
|
||||
themeRadius: Radius;
|
||||
} = {
|
||||
themeSize: 'small',
|
||||
themeRadius: 'medium',
|
||||
} as const;
|
||||
|
||||
const useTheme = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const size = (document.body.getAttribute('data-size') ??
|
||||
DefaultValue.themeSize) as Size;
|
||||
const radius = (document.body.getAttribute('data-radius') ??
|
||||
DefaultValue.themeRadius) as Radius;
|
||||
return { themeSize: size, themeRadius: radius };
|
||||
} else {
|
||||
return DefaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
export default useTheme;
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import type { IconNameType } from './components';
|
||||
import type { CaptionType } from './lib/types';
|
||||
import type { Size } from './types';
|
||||
|
||||
export const ICON_SIZE: Record<Size, number> = {
|
||||
large: 24,
|
||||
medium: 20,
|
||||
small: 16,
|
||||
};
|
||||
|
||||
export const SMALL_ICON_SIZE: Record<Size, number> = {
|
||||
large: 16,
|
||||
medium: 16,
|
||||
small: 12,
|
||||
};
|
||||
|
||||
export const CHECK_ICON_SIZE: Record<Size, number> = {
|
||||
large: 20,
|
||||
medium: 16,
|
||||
small: 14,
|
||||
};
|
||||
|
||||
export const INPUT_CAPTION_ICON_SIZE: Record<Size, number> = {
|
||||
large: 20,
|
||||
medium: 16,
|
||||
small: 16,
|
||||
};
|
||||
|
||||
export const CAPTION_DEFAULT_ICON: Record<
|
||||
CaptionType,
|
||||
IconNameType | undefined
|
||||
> = {
|
||||
default: undefined,
|
||||
error: 'RiErrorWarningFill',
|
||||
info: 'RiInformationFill',
|
||||
success: 'RiCheckboxCircleFill',
|
||||
} as const;
|
||||
|
||||
export const ALERT_DEFAULT_ICON: Record<string, IconNameType | undefined> = {
|
||||
default: undefined,
|
||||
warning: 'RiErrorWarningFill',
|
||||
informative: 'RiInformation2Fill',
|
||||
success: 'RiCheckboxCircleFill',
|
||||
error: 'RiCloseCircleFill',
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export * from './components';
|
||||
export * from './types';
|
||||
export * from './constants';
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export type Radius = 'small' | 'medium' | 'large';
|
||||
export type Size = 'small' | 'medium' | 'large';
|
||||
export type Color = 'default' | 'blue' | 'orange' | 'red' | 'green';
|
||||
export type ButtonVariant =
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'destructive'
|
||||
| 'ghost'
|
||||
| 'outline';
|
||||
export type ButtonState = 'default' | 'loading' | 'disabled';
|
||||
export type CaptionType = 'default' | 'success' | 'info' | 'error';
|
||||
export type AlertVariantType =
|
||||
| 'default'
|
||||
| 'warning'
|
||||
| 'success'
|
||||
| 'error'
|
||||
| 'informative';
|
||||
export type TriggerType = 'click' | 'hover';
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import type { ClassValue } from 'clsx';
|
||||
import { clsx } from 'clsx';
|
||||
import { extendTailwindMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
const customTwMerge = extendTailwindMerge({
|
||||
extend: {
|
||||
classGroups: {
|
||||
'font-size': [
|
||||
'text-title-h1',
|
||||
'text-title-h2',
|
||||
'text-title-h3',
|
||||
'text-title-h4',
|
||||
'text-title-h5',
|
||||
'text-small-normal',
|
||||
'text-small-strong',
|
||||
'text-small-underline',
|
||||
'text-small-delete',
|
||||
'text-base-normal',
|
||||
'text-base-strong',
|
||||
'text-base-underline',
|
||||
'text-base-delete',
|
||||
'text-large-normal',
|
||||
'text-large-strong',
|
||||
'text-large-underline',
|
||||
'text-large-delete',
|
||||
'text-xlarge-normal',
|
||||
'text-xlarge-strong',
|
||||
'text-xlarge-underline',
|
||||
'text-xlarge-delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return customTwMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
type PossibleRef<T> = React.Ref<T> | undefined;
|
||||
|
||||
/**
|
||||
* Set a given ref to a given value
|
||||
* This utility takes care of different types of refs: callback refs and RefObject(s)
|
||||
*/
|
||||
export function setRef<T>(ref: PossibleRef<T>, value: T) {
|
||||
if (typeof ref === 'function') {
|
||||
ref(value);
|
||||
} else if (ref !== null && ref !== undefined) {
|
||||
(ref as React.MutableRefObject<T>).current = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility to compose multiple refs together
|
||||
* Accepts callback refs and RefObject(s)
|
||||
*/
|
||||
export function composeRefs<T>(...refs: PossibleRef<T>[]) {
|
||||
return (node: T) => refs.forEach((ref) => setRef(ref, node));
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom hook that composes multiple refs
|
||||
* Accepts callback refs and RefObject(s)
|
||||
*/
|
||||
export function useComposedRefs<T>(...refs: PossibleRef<T>[]) {
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
return React.useCallback(composeRefs(...refs), refs);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export type Radius = 'small' | 'medium' | 'large';
|
||||
export type Size = 'small' | 'medium' | 'large';
|
||||
export type Color = 'default' | 'blue' | 'orange' | 'red' | 'green';
|
||||
export type ButtonVariant =
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'destructive'
|
||||
| 'ghost'
|
||||
| 'outline';
|
||||
@@ -0,0 +1,7 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: ['class'],
|
||||
prefix: '',
|
||||
content: ['./src/**/*.tsx'],
|
||||
plugins: [require('@ufb/tailwindcss')],
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@ufb/tsconfig/internal-package.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||
"jsx": "preserve",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import baseConfig from '@ufb/eslint-config/base';
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
];
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@ufb/shared",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format cjs,esm --dts",
|
||||
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
||||
"dev": "tsup src/index.ts --format cjs,esm --dts",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"prettier": "@ufb/prettier-config",
|
||||
"devDependencies": {
|
||||
"@ufb/eslint-config": "workspace:*",
|
||||
"@ufb/prettier-config": "workspace:*",
|
||||
"@ufb/tsconfig": "workspace:*",
|
||||
"eslint": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"react": "^19.2.4",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const Tenant = {
|
||||
TenantNotFound: 'TenantNotFound',
|
||||
TenantAlreadyExists: 'TenantAlreadyExists',
|
||||
};
|
||||
|
||||
const User = {
|
||||
UserAlreadyExists: 'UserAlreadyExists',
|
||||
UserNotFound: 'UserNotFound',
|
||||
PasswordNotMatched: 'PasswordNotMatched',
|
||||
EmailVerification: 'EmailVerification',
|
||||
EmailNotVerified: 'EmailNotVerified',
|
||||
PrivateServiceUserCreate: 'PrivateServiceUserCreate',
|
||||
NotAllowDomain: 'NotAllowDomain',
|
||||
InvalidCode: 'InvalidCode',
|
||||
InvalidPassword: 'InvalidPassword',
|
||||
};
|
||||
const Auth = {
|
||||
PasswordNotMatch: 'PasswordNotMatch',
|
||||
BlockedUser: 'BlockedUser',
|
||||
};
|
||||
|
||||
const Role = {
|
||||
RoleNotFound: 'RoleNotFound',
|
||||
OwnerIsImmutable: 'OwnerIsImmutable',
|
||||
RoleAlreadyExists: 'RoleAlreadyExists',
|
||||
};
|
||||
|
||||
const Mailing = {
|
||||
NotVerifiedEmail: 'NotVerifiedEmail',
|
||||
InvalidEmailCode: 'InvalidEmailCode',
|
||||
};
|
||||
|
||||
const Common = {
|
||||
InvalidDateFormat: 'InvalidDateFormat',
|
||||
};
|
||||
|
||||
const Project = {
|
||||
ProjectAlreadyExists: 'ProjectAlreadyExists',
|
||||
ProjectNotFound: 'ProjectNotFound',
|
||||
ProjectInvalidName: 'ProjectInvalidName',
|
||||
};
|
||||
|
||||
const Channel = {
|
||||
ChannelAlreadyExists: 'ChannelAlreadyExists',
|
||||
ChannelNotFound: 'ChannelNotFound',
|
||||
ChannelInvalidName: 'ChannelInvalidName',
|
||||
};
|
||||
|
||||
const Issue = {
|
||||
IssueNameDuplicated: 'IssueNameDuplicated',
|
||||
IssueInvalidName: 'IssueInvalidName',
|
||||
IssueNotFound: 'IssueNotFound',
|
||||
};
|
||||
|
||||
const Category = {
|
||||
CategoryNameDuplicated: 'CategoryNameDuplicated',
|
||||
CategoryNameInvalid: 'CategoryNameInvalid',
|
||||
CategoryNotFound: 'CategoryNotFound',
|
||||
};
|
||||
|
||||
const Field = {
|
||||
FieldNameDuplicated: 'FieldNameDuplicated',
|
||||
FieldKeyDuplicated: 'FieldKeyDuplicated',
|
||||
};
|
||||
|
||||
const Option = {
|
||||
OptionNameDuplicated: 'OptionNameDuplicated',
|
||||
OptionKeyDuplicated: 'OptionKeyDuplicated',
|
||||
};
|
||||
|
||||
const Feedback = {
|
||||
InvalidExpressionFormat: 'InvalidExpressionFormat',
|
||||
InvalidFieldType: 'InvalidFieldType',
|
||||
InvalidFieldRequest: 'InvalidFieldRequest',
|
||||
};
|
||||
|
||||
const Member = {
|
||||
MemberAlreadyExists: 'MemberAlreadyExists',
|
||||
MemberNotFound: 'MemberNotFound',
|
||||
MemberUpdateRoleNotMatchedProject: 'MemberUpdateRoleNotMatchedProject',
|
||||
MemberInvalidUser: 'MemberInvalidUser',
|
||||
};
|
||||
|
||||
const Opensearch = {
|
||||
LargeWindow: 'LargeWindow',
|
||||
};
|
||||
|
||||
const Webhook = {
|
||||
WebhookAlreadyExists: 'WebhookAlreadyExists',
|
||||
WebhookNotFound: 'WebhookNotFound',
|
||||
};
|
||||
|
||||
export const ErrorCode = {
|
||||
Tenant,
|
||||
Role,
|
||||
User,
|
||||
Auth,
|
||||
Mailing,
|
||||
Common,
|
||||
Feedback,
|
||||
Project,
|
||||
Channel,
|
||||
Issue,
|
||||
Category,
|
||||
Field,
|
||||
Option,
|
||||
Member,
|
||||
Opensearch,
|
||||
Webhook,
|
||||
};
|
||||
|
||||
export type ErrorCode = typeof ErrorCode;
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export * from './error-code.enum';
|
||||
export * from './timezone';
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export type TimezoneOffset =
|
||||
| '-12:00'
|
||||
| '-11:00'
|
||||
| '-10:00'
|
||||
| '-09:30'
|
||||
| '-09:00'
|
||||
| '-08:00'
|
||||
| '-07:00'
|
||||
| '-06:00'
|
||||
| '-05:00'
|
||||
| '-04:00'
|
||||
| '-03:30'
|
||||
| '-03:00'
|
||||
| '-02:00'
|
||||
| '-01:00'
|
||||
| '+00:00'
|
||||
| '+01:00'
|
||||
| '+02:00'
|
||||
| '+03:00'
|
||||
| '+03:30'
|
||||
| '+04:00'
|
||||
| '+04:30'
|
||||
| '+05:00'
|
||||
| '+05:30'
|
||||
| '+05:45'
|
||||
| '+06:00'
|
||||
| '+06:30'
|
||||
| '+07:00'
|
||||
| '+08:00'
|
||||
| '+08:45'
|
||||
| '+09:00'
|
||||
| '+09:30'
|
||||
| '+10:00'
|
||||
| '+10:30'
|
||||
| '+11:00'
|
||||
| '+12:00'
|
||||
| '+12:45'
|
||||
| '+13:00'
|
||||
| '+14:00';
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@ufb/tsconfig/internal-package.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import baseConfig from "@ufb/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [
|
||||
{
|
||||
ignores: [],
|
||||
},
|
||||
...baseConfig,
|
||||
];
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import type plugin from "tailwindcss/plugin";
|
||||
|
||||
declare const tailwindcss: ReturnType<typeof plugin>;
|
||||
|
||||
export default tailwindcss;
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import plugin from 'tailwindcss/plugin';
|
||||
|
||||
export default plugin(({ addBase, addComponents }) => {
|
||||
addBase(require('./dist/base.js'));
|
||||
addComponents(require('./dist/components.js'));
|
||||
addComponents(require('./dist/utilities.js'));
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@ufb/tailwindcss",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"typings": "index.d.ts",
|
||||
"files": [
|
||||
"dist/*.js",
|
||||
"index.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "pnpm build",
|
||||
"build": "node src/build",
|
||||
"clean": "rm -rf .turbo node_modules dist .cache"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ufb/eslint-config": "workspace:*",
|
||||
"@ufb/prettier-config": "workspace:*",
|
||||
"@ufb/tsconfig": "workspace:^",
|
||||
"autoprefixer": "^10.4.27",
|
||||
"eslint": "catalog:",
|
||||
"glob": "^13.0.6",
|
||||
"postcss": "^8.5.8",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-js": "^5.1.0",
|
||||
"postcss-nesting": "^14.0.0",
|
||||
"prettier": "catalog:",
|
||||
"tailwindcss": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
@layer {
|
||||
:root {
|
||||
/* primitives */
|
||||
--base-black: #000;
|
||||
--base-white: #fff;
|
||||
--base-transparent: transparent;
|
||||
--neutral50: #fafafa;
|
||||
--neutral100: #f5f5f5;
|
||||
--neutral200: #efefef;
|
||||
--neutral300: #dddcdc;
|
||||
--neutral400: #a3a3a3;
|
||||
--neutral500: #737373;
|
||||
--neutral600: #525252;
|
||||
--neutral700: #404040;
|
||||
--neutral800: #262626;
|
||||
--neutral900: #202020;
|
||||
--neutral950: #1c1c1c;
|
||||
--alpha-black50: rgb(0 0 0 / 8%);
|
||||
--alpha-black100: rgb(0 0 0 / 12%);
|
||||
--alpha-black200: rgb(0 0 0 / 16%);
|
||||
--alpha-black300: rgb(0 0 0 / 20%);
|
||||
--alpha-black400: rgb(0 0 0 / 24%);
|
||||
--alpha-black500: rgb(0 0 0 / 32%);
|
||||
--alpha-black600: rgb(0 0 0 / 46%);
|
||||
--alpha-black700: rgb(0 0 0 / 62%);
|
||||
--alpha-black800: rgb(0 0 0 / 71%);
|
||||
--alpha-black900: rgb(0 0 0 / 81%);
|
||||
--alpha-black950: rgb(0 0 0 / 90%);
|
||||
--alpha-white50: rgb(255 255 255 / 8%);
|
||||
--alpha-white100: rgb(255 255 255 / 12%);
|
||||
--alpha-white200: rgb(255 255 255 / 16%);
|
||||
--alpha-white300: rgb(255 255 255 / 20%);
|
||||
--alpha-white400: rgb(255 255 255 / 24%);
|
||||
--alpha-white500: rgb(255 255 255 / 32%);
|
||||
--alpha-white600: rgb(255 255 255 / 46%);
|
||||
--alpha-white700: rgb(255 255 255 / 62%);
|
||||
--alpha-white800: rgb(255 255 255 / 71%);
|
||||
--alpha-white900: rgb(255 255 255 / 81%);
|
||||
--alpha-white950: rgb(255 255 255 / 90%);
|
||||
--slate50: #f8fafc;
|
||||
--slate100: #f1f5f9;
|
||||
--slate200: #e2e8f0;
|
||||
--slate300: #cbd5e1;
|
||||
--slate400: #94a3b8;
|
||||
--slate500: #64748b;
|
||||
--slate600: #475569;
|
||||
--slate700: #334155;
|
||||
--slate800: #1e293b;
|
||||
--slate900: #0f172a;
|
||||
--slate950: #020617;
|
||||
--gray50: #f9fafb;
|
||||
--gray100: #f3f4f6;
|
||||
--gray200: #e5e7eb;
|
||||
--gray300: #d1d5db;
|
||||
--gray400: #9ca3af;
|
||||
--gray500: #6b7280;
|
||||
--gray600: #4b5563;
|
||||
--gray700: #374151;
|
||||
--gray800: #1f2937;
|
||||
--gray900: #111827;
|
||||
--gray950: #030712;
|
||||
--zinc50: #fafafa;
|
||||
--zinc100: #f4f4f5;
|
||||
--zinc200: #e4e4e7;
|
||||
--zinc300: #d4d4d8;
|
||||
--zinc400: #a1a1aa;
|
||||
--zinc500: #71717a;
|
||||
--zinc600: #52525b;
|
||||
--zinc700: #3f3f46;
|
||||
--zinc800: #27272a;
|
||||
--zinc900: #18181b;
|
||||
--zinc950: #09090b;
|
||||
--stone50: #fafaf9;
|
||||
--stone100: #f5f5f4;
|
||||
--stone200: #e7e5e4;
|
||||
--stone300: #d6d3d1;
|
||||
--stone400: #a8a29e;
|
||||
--stone500: #78716c;
|
||||
--stone600: #57534e;
|
||||
--stone700: #44403c;
|
||||
--stone800: #292524;
|
||||
--stone900: #1c1917;
|
||||
--stone950: #0c0a09;
|
||||
--red50: #fef2f2;
|
||||
--red100: #fee2e2;
|
||||
--red200: #fecaca;
|
||||
--red300: #fca5a5;
|
||||
--red400: #f87171;
|
||||
--red500: #ef4444;
|
||||
--red600: #dc2626;
|
||||
--red700: #b91c1c;
|
||||
--red800: #991b1b;
|
||||
--red900: #7f1d1d;
|
||||
--red950: #450a0a;
|
||||
--orange50: #fff7ed;
|
||||
--orange100: #ffedd5;
|
||||
--orange200: #fed7aa;
|
||||
--orange300: #fdba74;
|
||||
--orange400: #fb923c;
|
||||
--orange500: #f97316;
|
||||
--orange600: #ea580c;
|
||||
--orange700: #c2410c;
|
||||
--orange800: #9a3412;
|
||||
--orange900: #7c2d12;
|
||||
--orange950: #431407;
|
||||
--amber50: #fffbeb;
|
||||
--amber100: #fef3c7;
|
||||
--amber200: #fde68a;
|
||||
--amber300: #fcd34d;
|
||||
--amber400: #fbbf24;
|
||||
--amber500: #f59e0b;
|
||||
--amber600: #d97706;
|
||||
--amber700: #b45309;
|
||||
--amber800: #92400e;
|
||||
--amber900: #78350f;
|
||||
--amber950: #451a03;
|
||||
--yellow50: #fefce8;
|
||||
--yellow100: #fef9c3;
|
||||
--yellow200: #fef08a;
|
||||
--yellow300: #fde047;
|
||||
--yellow400: #facc15;
|
||||
--yellow500: #eab308;
|
||||
--yellow600: #ca8a04;
|
||||
--yellow700: #a16207;
|
||||
--yellow800: #854d0e;
|
||||
--yellow900: #713f12;
|
||||
--yellow950: #422006;
|
||||
--lime50: #f7fee7;
|
||||
--lime100: #ecfccb;
|
||||
--lime200: #d9f99d;
|
||||
--lime300: #bef264;
|
||||
--lime400: #a3e635;
|
||||
--lime500: #84cc16;
|
||||
--lime600: #65a30d;
|
||||
--lime700: #4d7c0f;
|
||||
--lime800: #3f6212;
|
||||
--lime900: #365314;
|
||||
--lime950: #1a2e05;
|
||||
--green50: #f0fdf4;
|
||||
--green100: #dcfce7;
|
||||
--green200: #bbf7d0;
|
||||
--green300: #86efac;
|
||||
--green400: #4ade80;
|
||||
--green500: #22c55e;
|
||||
--green600: #16a34a;
|
||||
--green700: #15803d;
|
||||
--green800: #166534;
|
||||
--green900: #14532d;
|
||||
--green950: #052e16;
|
||||
--emerald50: #ecfdf5;
|
||||
--emerald100: #d1fae5;
|
||||
--emerald200: #a7f3d0;
|
||||
--emerald300: #6ee7b7;
|
||||
--emerald400: #34d399;
|
||||
--emerald500: #10b981;
|
||||
--emerald600: #059669;
|
||||
--emerald700: #047857;
|
||||
--emerald800: #065f46;
|
||||
--emerald900: #064e3b;
|
||||
--emerald950: #022c22;
|
||||
--teal50: #f0fdfa;
|
||||
--teal100: #ccfbf1;
|
||||
--teal200: #99f6e4;
|
||||
--teal300: #5eead4;
|
||||
--teal400: #2dd4bf;
|
||||
--teal500: #14b8a6;
|
||||
--teal600: #0d9488;
|
||||
--teal700: #0f766e;
|
||||
--teal800: #115e59;
|
||||
--teal900: #134e4a;
|
||||
--teal950: #042f2e;
|
||||
--cyan50: #ecfeff;
|
||||
--cyan100: #cffafe;
|
||||
--cyan200: #a5f3fc;
|
||||
--cyan300: #67e8f9;
|
||||
--cyan400: #22d3ee;
|
||||
--cyan500: #06b6d4;
|
||||
--cyan600: #0891b2;
|
||||
--cyan700: #0e7490;
|
||||
--cyan800: #155e75;
|
||||
--cyan900: #164e63;
|
||||
--cyan950: #083344;
|
||||
--sky50: #f0f9ff;
|
||||
--sky100: #e0f2fe;
|
||||
--sky200: #bae6fd;
|
||||
--sky300: #7dd3fc;
|
||||
--sky400: #38bdf8;
|
||||
--sky500: #0ea5e9;
|
||||
--sky600: #0284c7;
|
||||
--sky700: #0369a1;
|
||||
--sky800: #075985;
|
||||
--sky900: #0c4a6e;
|
||||
--sky950: #082f49;
|
||||
--blue50: #eff6ff;
|
||||
--blue100: #dbeafe;
|
||||
--blue200: #bfdbfe;
|
||||
--blue300: #93c5fd;
|
||||
--blue400: #60a5fa;
|
||||
--blue500: #3b82f6;
|
||||
--blue600: #2563eb;
|
||||
--blue700: #1d4ed8;
|
||||
--blue800: #1e40af;
|
||||
--blue900: #1e3a8a;
|
||||
--blue950: #172554;
|
||||
--indigo50: #eef2ff;
|
||||
--indigo100: #e0e7ff;
|
||||
--indigo200: #c7d2fe;
|
||||
--indigo300: #a5b4fc;
|
||||
--indigo400: #818cf8;
|
||||
--indigo500: #6366f1;
|
||||
--indigo600: #4f46e5;
|
||||
--indigo700: #4338ca;
|
||||
--indigo800: #3730a3;
|
||||
--indigo900: #312e81;
|
||||
--indigo950: #1e1b4b;
|
||||
--violet50: #f5f3ff;
|
||||
--violet100: #ede9fe;
|
||||
--violet200: #ddd6fe;
|
||||
--violet300: #c4b5fd;
|
||||
--violet400: #a78bfa;
|
||||
--violet500: #8b5cf6;
|
||||
--violet600: #7c3aed;
|
||||
--violet700: #6d28d9;
|
||||
--violet800: #5b21b6;
|
||||
--violet900: #4c1d95;
|
||||
--violet950: #2e1065;
|
||||
--purple50: #faf5ff;
|
||||
--purple100: #f3e8ff;
|
||||
--purple200: #e9d5ff;
|
||||
--purple300: #d8b4fe;
|
||||
--purple400: #c084fc;
|
||||
--purple500: #a855f7;
|
||||
--purple600: #9333ea;
|
||||
--purple700: #7e22ce;
|
||||
--purple800: #6b21a8;
|
||||
--purple900: #581c87;
|
||||
--purple950: #3b0764;
|
||||
--fuchsia50: #fdf4ff;
|
||||
--fuchsia100: #fae8ff;
|
||||
--fuchsia200: #f5d0fe;
|
||||
--fuchsia300: #f0abfc;
|
||||
--fuchsia400: #e879f9;
|
||||
--fuchsia500: #d946ef;
|
||||
--fuchsia600: #c026d3;
|
||||
--fuchsia700: #a21caf;
|
||||
--fuchsia800: #86198f;
|
||||
--fuchsia900: #701a75;
|
||||
--fuchsia950: #4a044e;
|
||||
--pink50: #fdf2f8;
|
||||
--pink100: #fce7f3;
|
||||
--pink200: #fbcfe8;
|
||||
--pink300: #f9a8d4;
|
||||
--pink400: #f472b6;
|
||||
--pink500: #ec4899;
|
||||
--pink600: #db2777;
|
||||
--pink700: #be185d;
|
||||
--pink800: #9d174d;
|
||||
--pink900: #831843;
|
||||
--pink950: #500724;
|
||||
--rose50: #fff1f2;
|
||||
--rose100: #ffe4e6;
|
||||
--rose200: #fecdd3;
|
||||
--rose300: #fda4af;
|
||||
--rose400: #fb7185;
|
||||
--rose500: #f43f5e;
|
||||
--rose600: #e11d48;
|
||||
--rose700: #be123c;
|
||||
--rose800: #9f1239;
|
||||
--rose900: #881337;
|
||||
--rose950: #4c0519;
|
||||
|
||||
/* semantics */
|
||||
--fg-neutral-primary: var(--base-black);
|
||||
--fg-neutral-secondary: var(--neutral600);
|
||||
--fg-neutral-tertiary: var(--neutral500);
|
||||
--fg-neutral-inverse: var(--base-white);
|
||||
--fg-neutral-static: var(--base-white);
|
||||
--fg-tint-red: var(--red600);
|
||||
--fg-tint-orange: var(--amber600);
|
||||
--fg-tint-green: var(--green600);
|
||||
--fg-tint-blue: var(--blue600);
|
||||
--border-neutral-primary: var(--neutral950);
|
||||
--border-neutral-secondary: var(--neutral500);
|
||||
--border-neutral-tertiary: var(--neutral300);
|
||||
--border-neutral-transparent: var(--base-transparent);
|
||||
--border-tint-red: var(--red600);
|
||||
--border-tint-orange: var(--amber600);
|
||||
--border-tint-green: var(--green600);
|
||||
--border-tint-blue: var(--blue600);
|
||||
--bg-primary: var(--base-white);
|
||||
--bg-secondary: var(--neutral100);
|
||||
--bg-tertiary: var(--neutral200);
|
||||
--bg-dim: var(--alpha-black600);
|
||||
--bg-neutral-primary: var(--base-white);
|
||||
--bg-neutral-secondary: var(--neutral300);
|
||||
--bg-neutral-tertiary: var(--neutral200);
|
||||
--bg-neutral-inverse: var(--neutral950);
|
||||
--bg-neutral-hover: var(--alpha-black50);
|
||||
--bg-neutral-transparent: var(--base-transparent);
|
||||
--bg-tint-red-bold: var(--red600);
|
||||
--bg-tint-red-subtle: var(--red100);
|
||||
--bg-tint-red-hover: var(--red400);
|
||||
--bg-tint-orange-bold: var(--amber600);
|
||||
--bg-tint-orange-subtle: var(--amber100);
|
||||
--bg-tint-orange-hover: var(--amber400);
|
||||
--bg-tint-green-bold: var(--green600);
|
||||
--bg-tint-green-subtle: var(--green100);
|
||||
--bg-tint-green-hover: var(--green400);
|
||||
--bg-tint-blue-bold: var(--blue600);
|
||||
--bg-tint-blue-subtle: var(--blue100);
|
||||
--bg-tint-blue-hover: var(--blue400);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--fg-neutral-primary: var(--base-white);
|
||||
--fg-neutral-secondary: var(--neutral300);
|
||||
--fg-neutral-tertiary: var(--neutral400);
|
||||
--fg-neutral-inverse: var(--base-black);
|
||||
--fg-tint-red: var(--red500);
|
||||
--fg-tint-orange: var(--amber500);
|
||||
--fg-tint-green: var(--green500);
|
||||
--fg-tint-blue: var(--blue500);
|
||||
--border-neutral-primary: var(--neutral50);
|
||||
--border-neutral-secondary: var(--neutral500);
|
||||
--border-neutral-tertiary: var(--neutral600);
|
||||
--border-tint-red: var(--red500);
|
||||
--border-tint-orange: var(--amber500);
|
||||
--border-tint-green: var(--green500);
|
||||
--border-tint-blue: var(--blue500);
|
||||
--bg-primary: var(--neutral950);
|
||||
--bg-secondary: var(--neutral900);
|
||||
--bg-tertiary: var(--neutral800);
|
||||
--bg-dim: var(--alpha-black600);
|
||||
--bg-neutral-primary: var(--neutral800);
|
||||
--bg-neutral-secondary: var(--neutral700);
|
||||
--bg-neutral-tertiary: var(--neutral600);
|
||||
--bg-neutral-inverse: var(--neutral50);
|
||||
--bg-neutral-hover: var(--alpha-white50);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: [require('postcss-import'), require('tailwindcss/nesting')],
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
.text {
|
||||
&-small {
|
||||
&-normal {
|
||||
font-weight: 400;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.125rem;
|
||||
}
|
||||
|
||||
&-strong {
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-base {
|
||||
&-normal {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.375rem;
|
||||
}
|
||||
|
||||
&-strong {
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.375rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-large {
|
||||
&-normal {
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
&-strong {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-xlarge {
|
||||
&-normal {
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
&-strong {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
&-h1 {
|
||||
font-weight: 600;
|
||||
font-size: 2.375rem;
|
||||
line-height: 2.875rem;
|
||||
}
|
||||
|
||||
&-h2 {
|
||||
font-weight: 600;
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.375rem;
|
||||
}
|
||||
|
||||
&-h3 {
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
&-h4 {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
&-h5 {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
0: "0",
|
||||
4: "calc(var(--rounded-base, 0px) + 0.25rem)",
|
||||
6: "calc(var(--rounded-base, 0px) + 0.375rem)",
|
||||
8: "calc(var(--rounded-base, 0px) + 0.5rem)",
|
||||
12: "calc(var(--rounded-base, 0px) + 0.75rem)",
|
||||
16: "calc(var(--rounded-base, 0px) + 1rem)",
|
||||
24: "calc(var(--rounded-base, 0px) + 1.5rem)",
|
||||
full: "62.4375rem",
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
sm: "0px 1px 2px 0px rgba(0, 0, 0, 0.05)",
|
||||
default:
|
||||
"0px 1px 3px 0px rgba(0, 0, 0, 0.10), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)",
|
||||
md: "0px 4px 6px -1px rgba(0, 0, 0, 0.10), 0px 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
||||
lg: "0px 10px 15px -3px rgba(0, 0, 0, 0.10), 0px 4px 6px -2px rgba(0, 0, 0, 0.05)",
|
||||
xl: "0px 20px 25px -5px rgba(0, 0, 0, 0.10), 0px 10px 10px -5px rgba(0, 0, 0, 0.04)",
|
||||
"2xl": "0px 25px 50px -12px rgba(0, 0, 0, 0.25)",
|
||||
inner: "0px 2px 4px 0px rgba(0, 0, 0, 0.06) inset",
|
||||
none: "none",
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const postcss = require("postcss");
|
||||
const postcssJs = require("postcss-js");
|
||||
|
||||
const camelToKebab = (str) => {
|
||||
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
};
|
||||
|
||||
const transformKeys = (obj) => {
|
||||
if (typeof obj !== "object" || obj === null) return obj;
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(transformKeys);
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => {
|
||||
return [
|
||||
camelToKebab(key),
|
||||
typeof value === "object" ? transformKeys(value) : value,
|
||||
];
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const replaceApplyTrueWithEmptyObject = (obj) => {
|
||||
const stack = [obj];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const currentObj = stack.pop();
|
||||
for (const [key, value] of Object.entries(currentObj)) {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
stack.push(value);
|
||||
}
|
||||
|
||||
if (key.startsWith("@apply") && value === true) {
|
||||
currentObj[key] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function convertCssToJs(type = "base") {
|
||||
try {
|
||||
const inputPath = path.resolve(process.cwd(), `dist/${type}.css`);
|
||||
const outputPath = path.resolve(process.cwd(), `dist/${type}.js`);
|
||||
|
||||
// Read the CSS file
|
||||
const cssContent = await fs.readFile(inputPath, "utf-8");
|
||||
|
||||
// Parse the CSS and convert to JS object
|
||||
const root = postcss.parse(cssContent);
|
||||
const jsContent = postcssJs.objectify(root);
|
||||
const kebabCaseContent = transformKeys(jsContent);
|
||||
replaceApplyTrueWithEmptyObject(kebabCaseContent);
|
||||
|
||||
// Convert JS object to string and format as a module
|
||||
const jsOutput = `module.exports = ${JSON.stringify(kebabCaseContent, null, 2)};\n`;
|
||||
|
||||
// Ensure output directory exists
|
||||
const outputDir = path.dirname(outputPath);
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
|
||||
// Write the JS file
|
||||
await fs.writeFile(outputPath, jsOutput);
|
||||
|
||||
// console.log(`Successfully converted ${inputPath} to ${outputPath}`);
|
||||
} catch (error) {
|
||||
console.error(`Error generating JS from CSS: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = convertCssToJs;
|
||||
@@ -0,0 +1,58 @@
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const postcss = require("postcss");
|
||||
const glob = require("glob");
|
||||
/**
|
||||
* PostCSS를 사용하여 CSS 파일들을 처리하는 함수
|
||||
* 명령어 `postcss --config src/components src/components/*.css --base src --dir dist`와 동일한 기능을 수행합니다.
|
||||
*/
|
||||
async function generateCss(type) {
|
||||
try {
|
||||
// 설정 파일 로드
|
||||
const configPath = path.resolve(process.cwd(), `src/${type}`);
|
||||
const configFile = require(path.join(configPath, "postcss.config.js"));
|
||||
|
||||
// 플러그인 초기화
|
||||
const plugins = configFile.plugins || [];
|
||||
|
||||
// PostCSS 프로세서 생성
|
||||
const processor = postcss(plugins);
|
||||
const cssFiles = glob.sync(`src/${type}/*.css`);
|
||||
// 기본 경로
|
||||
const baseDir = path.resolve(process.cwd(), "src");
|
||||
const outputDir = path.resolve(process.cwd(), "dist");
|
||||
|
||||
// 디렉토리가 없으면 생성
|
||||
fs.mkdir(outputDir, { recursive: true });
|
||||
|
||||
// 각 파일 처리
|
||||
for (const file of cssFiles) {
|
||||
const css = await fs.readFile(file, "utf8");
|
||||
|
||||
const relativePath = path.relative(baseDir, file);
|
||||
const outputPath = path.join(outputDir, relativePath);
|
||||
|
||||
// 출력 디렉토리 확인
|
||||
const outputFileDir = path.dirname(outputPath);
|
||||
fs.mkdir(outputFileDir, { recursive: true });
|
||||
|
||||
// PostCSS 처리
|
||||
const result = await processor.process(css, {
|
||||
from: file,
|
||||
to: outputPath,
|
||||
map: { inline: false },
|
||||
});
|
||||
|
||||
// 처리된 CSS 저장
|
||||
await fs.writeFile(outputPath, result.css);
|
||||
// console.log(`Generated: ${outputPath}`);
|
||||
}
|
||||
|
||||
// console.log("CSS generation completed successfully.");
|
||||
} catch (error) {
|
||||
console.error("CSS generation failed:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = generateCss;
|
||||
@@ -0,0 +1,17 @@
|
||||
const log = {
|
||||
info: (message) =>
|
||||
console.log(
|
||||
`\x1b[36m[INFO]\x1b[0m ${new Date().toLocaleTimeString()} - ${message}`,
|
||||
),
|
||||
success: (message) =>
|
||||
console.log(
|
||||
`\x1b[32m[SUCCESS]\x1b[0m ${new Date().toLocaleTimeString()} - ${message}`,
|
||||
),
|
||||
error: (error) =>
|
||||
console.error(
|
||||
`\x1b[31m[ERROR]\x1b[0m ${new Date().toLocaleTimeString()} - ${error}`,
|
||||
),
|
||||
step: (step, total, message) =>
|
||||
console.log(`\x1b[35m[${step}/${total}]\x1b[0m \x1b[33m${message}\x1b[0m`),
|
||||
};
|
||||
module.exports = log;
|
||||
@@ -0,0 +1,45 @@
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const glob = require("glob");
|
||||
|
||||
async function mergeCss(type = "base") {
|
||||
try {
|
||||
const sourcePath = path.resolve(process.cwd(), `dist/${type}`);
|
||||
const outputPath = path.resolve(process.cwd(), `dist/${type}.css`);
|
||||
|
||||
// 출력 디렉토리 확인 및 생성
|
||||
const outputDir = path.dirname(outputPath);
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
|
||||
// CSS 파일 검색
|
||||
const cssFiles = glob.sync(path.join(sourcePath, "*.css"));
|
||||
|
||||
if (cssFiles.length === 0) {
|
||||
console.log(`No CSS files found in ${sourcePath} with pattern *.css`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 모든 CSS 내용을 담을 배열
|
||||
const cssContents = [];
|
||||
|
||||
// 각 파일의 내용 읽기
|
||||
for (const file of cssFiles) {
|
||||
const content = await fs.readFile(file, "utf8");
|
||||
cssContents.push(`/* ${path.basename(file)} */`);
|
||||
cssContents.push(content);
|
||||
}
|
||||
|
||||
// CSS 내용 병합
|
||||
const mergedCss = cssContents.join("\n\n");
|
||||
|
||||
// 병합된 CSS 저장
|
||||
await fs.writeFile(outputPath, mergedCss);
|
||||
|
||||
// console.log(`Merged CSS saved to: ${outputPath}`);
|
||||
} catch (error) {
|
||||
console.error("CSS merging failed:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mergeCss;
|
||||
@@ -0,0 +1,52 @@
|
||||
const generateCss = require("./build-functions/generate-css");
|
||||
const mergeCss = require("./build-functions/merge-css");
|
||||
const convertCssToJs = require("./build-functions/convert-css-to-js");
|
||||
const log = require("./build-functions/log");
|
||||
|
||||
async function executeCommands() {
|
||||
try {
|
||||
const totalSteps = 9;
|
||||
let currentStep = 0;
|
||||
|
||||
log.info("Starting CSS build process...");
|
||||
|
||||
// Base CSS processing
|
||||
log.step(++currentStep, totalSteps, "Generating base CSS files");
|
||||
await generateCss("base");
|
||||
|
||||
log.step(++currentStep, totalSteps, "Merging base CSS files");
|
||||
await mergeCss("base");
|
||||
|
||||
log.step(++currentStep, totalSteps, "Converting base CSS to JS");
|
||||
await convertCssToJs("base");
|
||||
log.success("Base CSS processing completed");
|
||||
|
||||
// Utilities CSS processing
|
||||
log.step(++currentStep, totalSteps, "Generating utilities CSS files");
|
||||
await generateCss("utilities");
|
||||
|
||||
log.step(++currentStep, totalSteps, "Merging utilities CSS files");
|
||||
await mergeCss("utilities");
|
||||
|
||||
log.step(++currentStep, totalSteps, "Converting utilities CSS to JS");
|
||||
await convertCssToJs("utilities");
|
||||
log.success("Utilities CSS processing completed");
|
||||
|
||||
// Components CSS processing
|
||||
log.step(++currentStep, totalSteps, "Generating components CSS files");
|
||||
await generateCss("components");
|
||||
|
||||
log.step(++currentStep, totalSteps, "Merging components CSS files");
|
||||
await mergeCss("components");
|
||||
|
||||
log.step(++currentStep, totalSteps, "Converting components CSS to JS");
|
||||
await convertCssToJs("components");
|
||||
log.success("Components CSS processing completed");
|
||||
|
||||
log.success("All CSS files built successfully!");
|
||||
} catch (error) {
|
||||
log.error(`Error executing commands: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
executeCommands();
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.accordion {
|
||||
--accordion-fg: var(--fg-neutral-primary);
|
||||
--accordion-border: var(--border-neutral-tertiary);
|
||||
--accordion-bg: var(--bg-primary);
|
||||
|
||||
@apply h-fit overflow-hidden bg-[var(--accordion-bg)] text-[var(--accordion-fg)];
|
||||
}
|
||||
|
||||
.accordion-border {
|
||||
@apply border border-[var(--accordion-border)] [&>div:last-of-type]:border-b-0;
|
||||
}
|
||||
|
||||
.accordion-item {
|
||||
@apply border-neutral-transparent;
|
||||
}
|
||||
|
||||
.accordion-item-border {
|
||||
@apply border-b border-[var(--accordion-border)];
|
||||
}
|
||||
|
||||
.accordion-trigger {
|
||||
@apply text-base-strong flex w-full items-center justify-between p-4 text-left transition-all [&>svg]:shrink-0 [&>svg]:transition-transform [&>svg]:duration-75 [&[data-state=open]>svg]:rotate-180;
|
||||
}
|
||||
|
||||
.accordion-trigger-align-left {
|
||||
@apply justify-start [&>svg:first-of-type]:mr-2;
|
||||
}
|
||||
|
||||
.accordion-content-box {
|
||||
@apply data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down text-base-normal overflow-hidden transition-all;
|
||||
}
|
||||
|
||||
.accordion-content {
|
||||
@apply p-4;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.alert {
|
||||
--alert-fg: var(--fg-neutral-primary);
|
||||
--alert-description-fg: var(--fg-neutral-secondary);
|
||||
--alert-fg-warning: var(--fg-tint-orange);
|
||||
--alert-fg-success: var(--fg-tint-green);
|
||||
--alert-fg-error: var(--fg-tint-red);
|
||||
--alert-fg-informative: var(--fg-tint-blue);
|
||||
--alert-border: var(--border-neutral-tertiary);
|
||||
--alert-border-warning: var(--border-tint-orange);
|
||||
--alert-border-success: var(--border-tint-green);
|
||||
--alert-border-error: var(--border-tint-red);
|
||||
--alert-border-informative: var(--border-tint-blue);
|
||||
--alert-bg: var(--bg-neutral-primary);
|
||||
|
||||
@apply relative inline-flex min-w-[356px] items-center space-x-2 border bg-[var(--alert-bg)] px-5 py-4;
|
||||
|
||||
/* default */
|
||||
@apply alert-radius-medium alert-default;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
@apply relative mb-auto box-content w-5 flex-shrink-0 p-1;
|
||||
|
||||
/* default */
|
||||
@apply alert-icon-default;
|
||||
}
|
||||
|
||||
.alert-loader {
|
||||
@apply flex scale-100;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
@apply flex flex-1 items-center space-x-2;
|
||||
}
|
||||
|
||||
.alert-text-container {
|
||||
@apply flex flex-1 flex-col items-start justify-center;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
@apply text-title-h5 flex flex-1 flex-col text-[var(--alert-fg)];
|
||||
}
|
||||
|
||||
.alert-description {
|
||||
@apply text-base-normal text-[var(--alert-description-fg)];
|
||||
}
|
||||
|
||||
.alert-button {
|
||||
@apply flex-shrink-0;
|
||||
}
|
||||
|
||||
.alert-close {
|
||||
@apply flex-shrink-0;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.badge {
|
||||
--badge-bold-fg: var(--fg-neutral-inverse);
|
||||
--badge-bold-fg-blue: var(--fg-neutral-inverse);
|
||||
--badge-bold-fg-orange: var(--fg-neutral-inverse);
|
||||
--badge-bold-fg-red: var(--fg-neutral-inverse);
|
||||
--badge-bold-fg-green: var(--fg-neutral-inverse);
|
||||
--badge-bold-border: var(--border-neutral-transparent);
|
||||
--badge-bold-border-blue: var(--border-neutral-transparent);
|
||||
--badge-bold-border-orange: var(--border-neutral-transparent);
|
||||
--badge-bold-border-red: var(--border-neutral-transparent);
|
||||
--badge-bold-border-green: var(--border-neutral-transparent);
|
||||
--badge-bold-bg: var(--bg-neutral-inverse);
|
||||
--badge-bold-bg-blue: var(--bg-tint-blue-bold);
|
||||
--badge-bold-bg-orange: var(--bg-tint-orange-bold);
|
||||
--badge-bold-bg-red: var(--bg-tint-red-bold);
|
||||
--badge-bold-bg-green: var(--bg-tint-green-bold);
|
||||
--badge-subtle-fg: var(--fg-neutral-primary);
|
||||
--badge-subtle-fg-blue: var(--fg-tint-blue);
|
||||
--badge-subtle-fg-orange: var(--fg-tint-orange);
|
||||
--badge-subtle-fg-red: var(--fg-tint-red);
|
||||
--badge-subtle-fg-green: var(--fg-tint-green);
|
||||
--badge-subtle-border: var(--border-neutral-transparent);
|
||||
--badge-subtle-border-blue: var(--border-neutral-transparent);
|
||||
--badge-subtle-border-orange: var(--border-neutral-transparent);
|
||||
--badge-subtle-border-red: var(--border-neutral-transparent);
|
||||
--badge-subtle-border-green: var(--border-neutral-transparent);
|
||||
--badge-subtle-bg: var(--bg-neutral-tertiary);
|
||||
--badge-subtle-bg-blue: var(--bg-tint-blue-subtle);
|
||||
--badge-subtle-bg-orange: var(--bg-tint-orange-subtle);
|
||||
--badge-subtle-bg-red: var(--bg-tint-red-subtle);
|
||||
--badge-subtle-bg-green: var(--bg-tint-green-subtle);
|
||||
--badge-outline-fg: var(--fg-neutral-primary);
|
||||
--badge-outline-fg-blue: var(--fg-tint-blue);
|
||||
--badge-outline-fg-orange: var(--fg-tint-orange);
|
||||
--badge-outline-fg-red: var(--fg-tint-red);
|
||||
--badge-outline-fg-green: var(--fg-tint-green);
|
||||
--badge-outline-border: var(--border-neutral-tertiary);
|
||||
--badge-outline-border-blue: var(--border-tint-blue);
|
||||
--badge-outline-border-orange: var(--border-tint-orange);
|
||||
--badge-outline-border-red: var(--border-tint-red);
|
||||
--badge-outline-border-green: var(--border-tint-green);
|
||||
--badge-outline-bg: var(--bg-neutral-primary);
|
||||
--badge-outline-bg-blue: var(--bg-neutral-primary);
|
||||
--badge-outline-bg-orange: var(--bg-neutral-primary);
|
||||
--badge-outline-bg-red: var(--bg-neutral-primary);
|
||||
--badge-outline-bg-green: var(--bg-neutral-primary);
|
||||
|
||||
@apply text-small-strong inline-flex whitespace-nowrap px-2 py-0.5;
|
||||
|
||||
/* default */
|
||||
@apply badge-bold-default badge-radius-medium;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.button {
|
||||
--button-primary-fg: var(--fg-neutral-inverse);
|
||||
--button-secondary-fg: var(--fg-neutral-primary);
|
||||
--button-destructive-fg: var(--fg-neutral-inverse);
|
||||
--button-ghost-fg: var(--fg-neutral-primary);
|
||||
--button-outline-fg: var(--tint, var(--fg-neutral-primary));
|
||||
--button-primary-border: var(--border-neutral-transparent);
|
||||
--button-secondary-border: var(--border-neutral-transparent);
|
||||
--button-destructive-border: var(--border-neutral-transparent);
|
||||
--button-ghost-border: var(--border-neutral-transparent);
|
||||
--button-outline-border: var(--border-neutral-tertiary);
|
||||
--button-primary-bg: var(--tint, var(--bg-neutral-inverse));
|
||||
--button-secondary-bg: var(--bg-neutral-tertiary);
|
||||
--button-destructive-bg: var(--bg-tint-red-bold);
|
||||
--button-ghost-bg: var(--bg-neutral-transparent);
|
||||
--button-ghost-bg-hover: var(--bg-neutral-secondary);
|
||||
--button-outline-bg: var(--bg-neutral-transparent);
|
||||
--button-outline-bg-hover: var(--tint-subtle, var(--bg-neutral-secondary));
|
||||
|
||||
@apply relative inline-flex h-fit cursor-pointer items-center justify-center transition-opacity disabled:cursor-not-allowed disabled:opacity-50 [&:not(:disabled)]:hover:opacity-80;
|
||||
|
||||
/* default */
|
||||
@apply button-small button-radius-medium button-primary;
|
||||
}
|
||||
|
||||
.button-loading {
|
||||
@apply absolute-center pointer-events-none flex shadow-none;
|
||||
|
||||
/* default */
|
||||
@apply button-loading-primary;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.calendar {
|
||||
--calendar-fg-default: var(--fg-neutral-primary);
|
||||
--calendar-fg-pressed: var(--fg-neutral-inverse);
|
||||
--calendar-week-fg: var(--fg-neutral-tertiary);
|
||||
--calendar-border: var(--border-neutral-tertiary);
|
||||
--calendar-bg: var(--bg-neutral-primary);
|
||||
--calendar-bg-pressed: var(--tint, var(--bg-neutral-inverse));
|
||||
--calendar-bg-hover: var(--bg-neutral-tertiary);
|
||||
--calendar-bg-range: var(--bg-neutral-tertiary);
|
||||
|
||||
@apply rounded-8 relative box-content w-fit border border-[var(--calendar-border)] bg-[var(--calendar-bg)] p-2 shadow-sm;
|
||||
}
|
||||
|
||||
.calendar-months {
|
||||
@apply flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0;
|
||||
}
|
||||
|
||||
.calendar-month {
|
||||
@apply min-w-[15.75rem] space-y-4;
|
||||
}
|
||||
|
||||
.calendar-caption {
|
||||
@apply relative flex items-center justify-center pt-1;
|
||||
}
|
||||
|
||||
.calendar-caption-label {
|
||||
@apply text-base-strong text-[var(--calendar-fg-default)];
|
||||
}
|
||||
|
||||
.calendar-nav {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.calendar-nav-button {
|
||||
@apply flex p-2.5 hover:opacity-50;
|
||||
}
|
||||
|
||||
.calendar-nav-button-previous {
|
||||
@apply absolute left-0;
|
||||
}
|
||||
|
||||
.calendar-nav-button-next {
|
||||
@apply absolute right-0;
|
||||
}
|
||||
|
||||
.calendar-table {
|
||||
@apply !mt-4 w-full border-collapse;
|
||||
}
|
||||
|
||||
.calendar-head-row {
|
||||
@apply flex;
|
||||
}
|
||||
|
||||
.calendar-head-cell {
|
||||
@apply text-small-normal flex-1 px-2 py-0.5 font-normal text-[var(--calendar-week-fg)];
|
||||
}
|
||||
|
||||
.calendar-row {
|
||||
@apply mt-0.5 flex w-full;
|
||||
}
|
||||
|
||||
.calendar-cell {
|
||||
@apply [&:has([aria-selected].day-range-end)]:rounded-r-8 first:[&:has([aria-selected])]:rounded-l-8 last:[&:has([aria-selected])]:rounded-r-8 [&:has(.calendar-day-range-start)]:rounded-l-8 [&:has(.calendar-day-range-end)]:rounded-r-8 text-base-normal relative aspect-square flex-1 p-0 text-center focus-within:relative focus-within:z-20 [&:has(.calendar-day-range-end)]:bg-[var(--calendar-bg-hover)] [&:has(.calendar-day-range-start)]:bg-[var(--calendar-bg-hover)];
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
@apply rounded-8 text-base-normal relative flex h-full w-full items-center justify-center overflow-hidden p-0 transition-colors aria-selected:opacity-100 [&:not(.calendar-day-selected)]:hover:bg-[var(--calendar-bg-hover)];
|
||||
}
|
||||
|
||||
.calendar-day-selected:not(.calendar-day-outside) {
|
||||
@apply bg-[var(--calendar-bg-pressed)] text-[--calendar-fg-pressed];
|
||||
}
|
||||
|
||||
.calendar-day-today {
|
||||
@apply before:absolute before:left-1/2 before:top-0 before:flex before:h-1 before:w-1 before:-translate-x-1/2 before:rounded-full before:bg-[var(--calendar-bg-pressed)];
|
||||
}
|
||||
|
||||
.calendar-day-outside {
|
||||
@apply pointer-events-none !opacity-50;
|
||||
}
|
||||
|
||||
.calendar-day-disabled {
|
||||
@apply opacity-50;
|
||||
}
|
||||
|
||||
.calendar-day-range-middle {
|
||||
@apply rounded-0 aria-selected:bg-[var(--calendar-bg-hover)] aria-selected:text-[var(--calendar-fg-default)];
|
||||
}
|
||||
|
||||
.calendar-day-hidden {
|
||||
@apply invisible;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.caption {
|
||||
--caption-fg: var(--fg-neutral-tertiary);
|
||||
--caption-fg-success: var(--fg-tint-green);
|
||||
--caption-fg-error: var(--fg-tint-red);
|
||||
|
||||
@apply text-base-normal flex items-center gap-1;
|
||||
|
||||
/* default */
|
||||
@apply caption-default;
|
||||
}
|
||||
|
||||
.caption-icon {
|
||||
@apply flex-shrink-0;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.checkbox {
|
||||
--checkbox-fg: var(--fg-neutral-primary);
|
||||
--checkbox-fg-checked: var(--fg-neutral-primary);
|
||||
--checkbox-fg-indeterminate: var(--fg-neutral-primary);
|
||||
--checkbox-icon: var(--fg-neutral-inverse);
|
||||
--checkbox-icon-checked: var(--fg-neutral-inverse);
|
||||
--checkbox-border-default: var(--border-neutral-secondary);
|
||||
--checkbox-bg: var(--bg-neutral-primary);
|
||||
--checkbox-bg-checked: var(--tint, var(--bg-neutral-inverse));
|
||||
|
||||
@apply inline-flex cursor-pointer items-center text-[var(--checkbox-fg)] transition-opacity disabled:cursor-not-allowed disabled:opacity-50 [&:not(:disabled)]:hover:opacity-80;
|
||||
|
||||
/* default */
|
||||
@apply checkbox-small;
|
||||
}
|
||||
|
||||
.check {
|
||||
@apply before:rounded-4 relative mr-2 flex items-center justify-center text-[var(--checkbox-icon)] before:absolute before:inset-0 before:flex before:border before:border-[var(--checkbox-border-default)];
|
||||
|
||||
/* default */
|
||||
@apply check-small;
|
||||
}
|
||||
|
||||
.checkbox-icon {
|
||||
@apply rounded-4 absolute left-0 top-0 flex h-full w-full items-center justify-center bg-[var(--checkbox-bg-checked)] text-[var(--checkbox-icon-checked)];
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.combobox-content {
|
||||
--combobox-fg: var(--fg-neutral-primary);
|
||||
--combobox-fg-empty: var(--fg-neutral-secondary);
|
||||
--combobox-fg-placeholder: var(--fg-neutral-tertiary);
|
||||
--combobox-shortcut-fg: var(--fg-neutral-tertiary);
|
||||
--combobox-bg: var(--bg-neutral-primary);
|
||||
--combobox-bg-hover: var(--bg-neutral-tertiary);
|
||||
--combobox-border: var(--border-neutral-tertiary);
|
||||
|
||||
@apply flex h-full min-w-[--radix-popover-trigger-width] flex-col overflow-hidden p-1 text-[var(--combobox-fg)];
|
||||
}
|
||||
|
||||
.combobox-empty {
|
||||
@apply text-base-normal flex items-center justify-center p-6 text-center text-[var(--combobox-fg-empty)];
|
||||
}
|
||||
|
||||
.combobox-item {
|
||||
@apply rounded-6 text-base-normal relative flex cursor-pointer select-none items-center p-2 outline-none transition-colors hover:bg-[var(--combobox-bg-hover)] data-[selected=true]:bg-[var(--combobox-bg-hover)];
|
||||
}
|
||||
|
||||
.combobox-item ~ .combobox-item {
|
||||
@apply mt-1;
|
||||
}
|
||||
|
||||
.combobox-check {
|
||||
@apply mr-2;
|
||||
}
|
||||
|
||||
.combobox-caption {
|
||||
@apply text-small-normal ml-auto tracking-widest text-[var(--combobox-shortcut-fg)];
|
||||
}
|
||||
|
||||
.combobox-separator {
|
||||
@apply -mx-1 my-1 h-px bg-[var(--combobox-border)];
|
||||
}
|
||||
|
||||
.combobox-group {
|
||||
@apply [&_[cmdk-group-heading]]:text-base-strong overflow-hidden p-[1px] text-[var(--combobox-fg)] [&_[cmdk-group-heading]]:p-2;
|
||||
}
|
||||
|
||||
.combobox-list {
|
||||
@apply pt-1;
|
||||
}
|
||||
|
||||
.combobox-input-box {
|
||||
@apply relative flex items-center justify-center border-b border-b-[var(--combobox-border)] bg-[var(--combobox-bg)] [&>svg]:ml-2 [&>svg]:shrink-0 [&>svg]:opacity-50;
|
||||
}
|
||||
|
||||
.combobox-input {
|
||||
@apply text-base-normal min-w-0 flex-1 bg-[transparent] p-2 text-[var(--combobox-fg)] outline-none placeholder:text-[var(--combobox-fg-placeholder)];
|
||||
}
|
||||
|
||||
.combobox-trigger {
|
||||
--combobox-border-error: var(--border-tint-red);
|
||||
|
||||
@apply [&>svg:last-of-type]:!ml-auto;
|
||||
}
|
||||
|
||||
.combobox-trigger[aria-invalid='true'] {
|
||||
@apply !border-2 !border-[var(--combobox-border-error)];
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.dialog {
|
||||
--dialog-bg: var(--bg-neutral-primary);
|
||||
--dialog-border: var(--border-neutral-tertiary);
|
||||
|
||||
@apply data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-[var(--alpha-black800)];
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
--dialog-bg: var(--bg-neutral-primary);
|
||||
--dialog-border: var(--border-neutral-tertiary);
|
||||
|
||||
@apply data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-sm translate-x-[-50%] translate-y-[-50%] gap-y-5 space-y-2 border border-[var(--dialog-border)] bg-[var(--dialog-bg)] p-5 duration-200;
|
||||
|
||||
/* default */
|
||||
@apply dialog-content-radius-medium;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
@apply flex flex-col gap-1 text-left;
|
||||
}
|
||||
|
||||
.dialog-icon {
|
||||
@apply p-1;
|
||||
|
||||
/* default */
|
||||
@apply dialog-icon-default;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
@apply text-title-h4 text-[var(--fg-neutral-primary)];
|
||||
}
|
||||
|
||||
.dialog-description {
|
||||
@apply text-base-normal text-[var(--fg-neutral-secondary)];
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
@apply space-y-3;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
@apply flex flex-wrap gap-2;
|
||||
|
||||
/* default */
|
||||
@apply dialog-footer-right;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
@apply !absolute right-2 top-2 !m-0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.divider {
|
||||
--divider-border-bold: var(--border-neutral-primary);
|
||||
--divider-border-subtle: var(--border-neutral-tertiary);
|
||||
|
||||
@apply shrink-0;
|
||||
|
||||
/* default */
|
||||
@apply [&:not(.divider-vertical)]:divider-horizontal divider-bold;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.dropdown-content {
|
||||
--dropdown-fg: var(--fg-neutral-primary);
|
||||
--dropdown-fg-pressed: var(--tint, var(--fg-neutral-primary));
|
||||
--dropdown-fg-empty: var(--fg-neutral-secondary);
|
||||
--dropdown-shortcut-fg: var(--fg-neutral-tertiary);
|
||||
--dropdown-bg: var(--bg-neutral-primary);
|
||||
--dropdown-bg-hover: var(--bg-neutral-tertiary);
|
||||
--dropdown-bg-pressed: var(--bg-neutral-tertiary);
|
||||
--dropdown-border: var(--border-neutral-tertiary);
|
||||
|
||||
@apply data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95;
|
||||
@apply data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95;
|
||||
@apply data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2;
|
||||
@apply rounded-8 z-50 min-w-[--radix-dropdown-menu-trigger-width] overflow-hidden border p-1 shadow-md;
|
||||
@apply border-[var(--dropdown-border)] bg-[var(--dropdown-bg)] text-[var(--dropdown-fg)];
|
||||
}
|
||||
|
||||
.dropdown-sub-trigger {
|
||||
@apply rounded-6 text-base-normal flex cursor-pointer select-none items-center p-2 outline-none focus:bg-[var(--dropdown-bg-hover)] data-[state=open]:bg-[var(--dropdown-bg-pressed)] data-[state=open]:text-[var(--dropdown-fg-pressed)];
|
||||
}
|
||||
|
||||
.dropdown-sub-content {
|
||||
--dropdown-fg: var(--fg-neutral-primary);
|
||||
--dropdown-fg-pressed: var(--tint, var(--fg-neutral-primary));
|
||||
--dropdown-fg-empty: var(--fg-neutral-secondary);
|
||||
--dropdown-shortcut-fg: var(--fg-neutral-tertiary);
|
||||
--dropdown-bg: var(--bg-neutral-primary);
|
||||
--dropdown-bg-hover: var(--bg-neutral-tertiary);
|
||||
--dropdown-bg-pressed: var(--bg-neutral-tertiary);
|
||||
--dropdown-border: var(--border-neutral-tertiary);
|
||||
|
||||
@apply data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95;
|
||||
@apply data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95;
|
||||
@apply data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2;
|
||||
@apply data-[side=bottom]:mt-1 data-[side=left]:mr-1 data-[side=right]:ml-1 data-[side=top]:mb-1;
|
||||
@apply rounded-8 z-50 min-w-[--radix-dropdown-menu-trigger-width] overflow-hidden border p-1 shadow-lg;
|
||||
@apply border-[var(--dropdown-border)] bg-[var(--dropdown-bg)] text-[var(--dropdown-fg)];
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
@apply rounded-6 text-base-normal relative flex cursor-pointer select-none items-center p-2 outline-none transition-colors focus:bg-[var(--dropdown-bg-hover)] data-[disabled]:pointer-events-none data-[state=on]:bg-[var(--dropdown-bg-pressed)] data-[state=on]:text-[var(--dropdown-fg-pressed)] data-[disabled]:opacity-50;
|
||||
@apply [&>svg]:h-4 [&>svg]:w-4;
|
||||
}
|
||||
|
||||
.dropdown-checkbox {
|
||||
@apply rounded-8 text-base-normal relative flex cursor-pointer select-none items-center py-2 pl-8 outline-none transition-colors focus:bg-[var(--dropdown-bg-hover)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50;
|
||||
}
|
||||
|
||||
.dropdown-checkbox-icon {
|
||||
@apply absolute left-2 flex h-4 w-4 items-center justify-center;
|
||||
}
|
||||
|
||||
.dropdown-radio {
|
||||
@apply rounded-8 text-base-normal relative flex cursor-pointer select-none items-center py-2 pl-8 outline-none transition-colors focus:bg-[var(--dropdown-bg-hover)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50;
|
||||
}
|
||||
|
||||
.dropdown-radio-icon {
|
||||
@apply absolute left-3.5 flex h-2 w-2 items-center justify-center;
|
||||
}
|
||||
|
||||
.dropdown-item ~ .dropdown-item {
|
||||
@apply mt-1;
|
||||
}
|
||||
|
||||
.dropdown-label {
|
||||
@apply text-base-strong space-x-2 p-2;
|
||||
}
|
||||
|
||||
.dropdown-caption {
|
||||
@apply text-small-normal ml-auto tracking-widest text-[var(--dropdown-shortcut-fg)];
|
||||
}
|
||||
|
||||
.dropdown-separator {
|
||||
@apply -mx-1 my-1 h-px bg-[var(--dropdown-border)];
|
||||
}
|
||||
|
||||
.dropdown-empty {
|
||||
@apply text-base-normal flex items-center justify-center p-6 text-[var(--dropdown-fg-empty)];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.icon {
|
||||
@apply pointer-events-none inline-flex text-inherit;
|
||||
}
|
||||
|
||||
.icon-clickable {
|
||||
@apply pointer-events-auto cursor-pointer;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.input-field {
|
||||
--input-fg: var(--fg-neutral-tertiary);
|
||||
--input-fg-typing: var(--fg-neutral-primary);
|
||||
--input-fg-filled: var(--fg-neutral-primary);
|
||||
--input-border: var(--border-neutral-tertiary);
|
||||
--input-border-typing: var(--tint, var(--border-neutral-primary));
|
||||
--input-border-filled: var(--border-neutral-tertiary);
|
||||
--input-border-error: var(--border-tint-red);
|
||||
--input-bg: var(--bg-neutral-primary);
|
||||
--input-bg-typing: var(--bg-neutral-tertiary);
|
||||
--input-bg-filled: var(--bg-neutral-primary);
|
||||
|
||||
@apply flex flex-col gap-1.5;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
@apply relative;
|
||||
@apply [&:has(.input-button)>.input-large]:!pr-12 [&:has(.input-button)>.input-medium]:!pr-10 [&:has(.input-button)>.input-small]:!pr-8;
|
||||
@apply [&:has(.input-icon)>.input-large]:!pl-12 [&:has(.input-icon)>.input-medium]:!pl-10 [&:has(.input-icon)>.input-small]:!pl-8;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply text-[var(--input-fg-filled)] placeholder:text-[var(--input-fg)] placeholder-shown:text-[var(--input-fg)] focus-visible:text-[var(--input-fg-typing)];
|
||||
@apply border border-[var(--input-border-filled)] placeholder-shown:border placeholder-shown:border-[var(--input-border)] focus-visible:border-2 focus-visible:border-[var(--input-border-typing)];
|
||||
@apply bg-[var(--input-bg-filled)] placeholder-shown:bg-[var(--input-bg)] focus-visible:bg-[var(--input-bg-typing)];
|
||||
|
||||
@apply w-full outline-none;
|
||||
@apply disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-search-cancel-button]:hidden;
|
||||
|
||||
/* default */
|
||||
@apply input-small input-radius-medium;
|
||||
}
|
||||
.input[aria-invalid='true'] {
|
||||
@apply !border-2 !border-[var(--input-border-error)] !bg-[var(--input-bg-error)];
|
||||
}
|
||||
|
||||
.input-button {
|
||||
@apply absolute-y-center flex flex-shrink-0 items-center justify-center disabled:cursor-not-allowed disabled:opacity-50;
|
||||
|
||||
/* default */
|
||||
@apply input-button-small;
|
||||
}
|
||||
|
||||
.input-button.show-only-on-focus-and-has-value {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.input-box:focus-within
|
||||
.input:not(:placeholder-shown)
|
||||
~ .input-button.show-only-on-focus-and-has-value {
|
||||
@apply flex;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
@apply absolute-y-center flex;
|
||||
|
||||
/* default */
|
||||
@apply input-icon-small;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.label {
|
||||
--label-fg: var(--fg-neutral-primary);
|
||||
--label-fg-error: var(--fg-tint-red);
|
||||
|
||||
@apply text-base-normal font-normal text-[var(--label-fg)] data-[error=true]:text-[var(--label-fg-error)];
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.menu {
|
||||
--menu-fg: var(--fg-neutral-tertiary);
|
||||
--menu-fg-hover: var(--fg-neutral-tertiary);
|
||||
--menu-fg-pressed: var(--fg-neutral-primary);
|
||||
--menu-bg-hover: var(--bg-neutral-tertiary);
|
||||
--menu-bg-pressed: var(--bg-neutral-tertiary);
|
||||
|
||||
@apply rounded-8 inline-flex px-2 py-3;
|
||||
|
||||
/* default */
|
||||
@apply [&:not(.menu-vertical)]:menu-horizontal;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
@apply rounded-8 relative flex w-full cursor-pointer select-none items-center gap-2 text-left text-[var(--menu-fg)] outline-none hover:bg-[var(--menu-bg-hover)] hover:text-[var(--menu-fg-hover)] hover:opacity-100 data-[disabled]:pointer-events-none data-[state=on]:bg-[var(--menu-bg-pressed)] data-[state=open]:!bg-[var(--menu-bg-pressed)] data-[state=on]:text-[var(--menu-fg-pressed)] data-[state=open]:text-[var(--menu-fg-pressed)] data-[disabled]:opacity-50 [&>svg]:flex-shrink-0;
|
||||
|
||||
/* default */
|
||||
@apply menu-item-small;
|
||||
}
|
||||
|
||||
.menu-dropdown-item {
|
||||
@apply !p-0;
|
||||
}
|
||||
|
||||
.menu-dropdown-item ~ .menu-dropdown-item {
|
||||
@apply mt-1;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/* todo remove */
|
||||
.navbar {
|
||||
@apply border-b-neutral-tertiary bg-neutral-primary flex h-14 items-center border-b px-6 py-2.5;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
@apply !p-0;
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
@apply flex p-1.5;
|
||||
}
|
||||
|
||||
.navbar-divider {
|
||||
@apply mx-1;
|
||||
}
|
||||
|
||||
.navbar-dropdown {
|
||||
@apply ml-auto;
|
||||
}
|
||||
|
||||
.navbar-dropdown ~ .navbar-button {
|
||||
@apply ml-3;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.pagination {
|
||||
@apply mx-auto flex w-full justify-center;
|
||||
}
|
||||
|
||||
.pagination-content {
|
||||
@apply flex flex-row items-center space-x-1;
|
||||
}
|
||||
|
||||
.pagination-item {
|
||||
@apply flex-shrink-0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.popover {
|
||||
@apply data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95;
|
||||
@apply data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95;
|
||||
@apply data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2;
|
||||
@apply rounded-8 z-50 min-w-32 overflow-hidden border border-[var(--border-neutral-tertiary)] bg-[var(--bg-neutral-primary)] shadow-md;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('postcss-import'),
|
||||
require('tailwindcss/nesting'),
|
||||
require('tailwindcss')('./src/components/tailwind.config.js'),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.radio-card-group {
|
||||
--radio-card-text-title: var(--fg-neutral-primary);
|
||||
--radio-card-text-description: var(--fg-neutral-tertiary);
|
||||
--radio-card-border-default: var(--border-neutral-tertiary);
|
||||
--radio-card-border-selected: var(--tint, var(--border-neutral-primary));
|
||||
--radio-card-fg-default: var(--bg-neutral-primary);
|
||||
--radio-card-fg-hover: var(--bg-neutral-tertiary);
|
||||
|
||||
@apply inline-flex;
|
||||
|
||||
/* default */
|
||||
@apply [&:not(.radio-card-group-vertical)]:radio-card-group-horizontal;
|
||||
}
|
||||
|
||||
.radio-card {
|
||||
@apply relative inline-flex border border-[var(--radio-card-border-default)] bg-[var(--radio-card-fg-default)] transition-colors disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-2 data-[state=checked]:border-[var(--radio-card-border-selected)] [&:not(:disabled)]:hover:bg-[var(--radio-card-fg-hover)];
|
||||
|
||||
/* default */
|
||||
@apply [&:not(.radio-card-horizontal)]:radio-card-vertical radio-card-radius-medium;
|
||||
}
|
||||
|
||||
.radio-card-text {
|
||||
@apply block text-inherit;
|
||||
}
|
||||
|
||||
.radio-card-title {
|
||||
@apply text-base-strong block text-[var(--radio-card-text-title)];
|
||||
}
|
||||
|
||||
.radio-card-description {
|
||||
@apply text-base-normal block text-[var(--radio-card-text-description)];
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.radio-group {
|
||||
--radio-fg: var(--fg-neutral-primary);
|
||||
--radio-border: var(--border-neutral-tertiary);
|
||||
--radio-bg: var(--bg-neutral-primary);
|
||||
--radio-bg-select: var(--tint, var(--bg-neutral-inverse));
|
||||
|
||||
@apply flex;
|
||||
|
||||
/* default */
|
||||
@apply [&:not(.radio-group-vertical)]:radio-group-horizontal;
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
@apply text-base-normal flex items-center py-2 text-[var(--radio-fg)] disabled:cursor-not-allowed disabled:opacity-50;
|
||||
}
|
||||
|
||||
.radio {
|
||||
@apply relative mr-2 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-[var(--radio-bg)] before:absolute before:inset-0 before:flex before:rounded-full before:border before:border-[var(--radio-border)];
|
||||
}
|
||||
|
||||
.radio-indicator {
|
||||
@apply absolute left-0 top-0 flex h-full w-full items-center justify-center rounded-full bg-[var(--radio-bg-select)] text-[var(--radio-bg)] focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.scroll-area {
|
||||
--scroll-area-bg: var(--bg-neutral-transparent);
|
||||
--scroll-area-thumb-bg: var(--bg-neutral-tertiary);
|
||||
|
||||
@apply relative overflow-hidden bg-[var(--scroll-area-bg)];
|
||||
}
|
||||
|
||||
.scroll-area-viewport {
|
||||
@apply h-full w-full rounded-[inherit] has-[.scroll-bar-horizontal]:pb-2 has-[.scroll-bar-vertical]:pr-2;
|
||||
}
|
||||
|
||||
.scroll-bar {
|
||||
@apply flex touch-none select-none transition-colors;
|
||||
|
||||
/* default */
|
||||
@apply [&:not(.scroll-bar-horizontal)]:scroll-bar-vertical;
|
||||
}
|
||||
|
||||
.scroll-thumb {
|
||||
@apply relative flex-1 rounded-full bg-[var(--scroll-area-thumb-bg)];
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
.select {
|
||||
/* default */
|
||||
@apply select-small;
|
||||
}
|
||||
|
||||
.select-trigger {
|
||||
--select-fg: var(--fg-neutral-tertiary);
|
||||
--select-fg-pressed: var(--fg-neutral-primary);
|
||||
--select-fg-filled: var(--fg-neutral-primary);
|
||||
--select-border: var(--border-neutral-tertiary);
|
||||
--select-border-pressed: var(--tint, var(--border-neutral-primary));
|
||||
--select-border-error: var(--border-tint-red);
|
||||
--select-bg: var(--bg-neutral-primary);
|
||||
--select-bg-pressed: var(--bg-neutral-tertiary);
|
||||
|
||||
@apply relative flex w-full items-center justify-start bg-[var(--select-bg)] text-left text-[var(--select-fg-filled)] transition-transform before:absolute before:inset-0 before:flex before:border before:border-[var(--select-border)] focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-[var(--select-fg)] data-[state=open]:text-[var(--select-fg-pressed)] data-[state=open]:before:border-2 data-[state=open]:before:border-[var(--select-border-pressed)] [&>span]:flex [&>span]:flex-1 [&>span]:items-center [&>span]:overflow-hidden [&>svg:last-of-type]:data-[state=open]:rotate-180;
|
||||
|
||||
/* default */
|
||||
@apply select-trigger-medium select-trigger-radius-medium;
|
||||
}
|
||||
|
||||
.select-trigger[aria-invalid='true'] {
|
||||
@apply before:!border-2 before:!border-[var(--select-border-error)];
|
||||
}
|
||||
|
||||
.select-content {
|
||||
--select-fg: var(--fg-neutral-tertiary);
|
||||
--select-fg-pressed: var(--fg-neutral-primary);
|
||||
--select-fg-filled: var(--fg-neutral-primary);
|
||||
--select-border: var(--border-neutral-tertiary);
|
||||
--select-border-pressed: var(--tint, var(--border-neutral-primary));
|
||||
--select-border-error: var(--border-tint-red);
|
||||
--select-bg: var(--bg-neutral-primary);
|
||||
--select-bg-pressed: var(--bg-neutral-tertiary);
|
||||
|
||||
@apply data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95;
|
||||
@apply data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95;
|
||||
@apply data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:mt-1 data-[side=top]:mb-1;
|
||||
@apply rounded-8 relative z-50 !min-w-[var(--radix-select-trigger-width,_var(--radix-popper-anchor-width))] overflow-hidden border border-[var(--select-border)] bg-[var(--select-bg)] p-1 text-[var(--select-fg-filled)] shadow-md;
|
||||
}
|
||||
|
||||
.select-viewport {
|
||||
@apply p-px;
|
||||
}
|
||||
|
||||
.select-group-label {
|
||||
@apply text-base-strong p-2;
|
||||
}
|
||||
|
||||
.select-item {
|
||||
@apply rounded-6 text-base-normal relative flex cursor-pointer select-none items-center py-2 outline-none transition-colors hover:bg-[var(--select-bg-pressed)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50;
|
||||
|
||||
/* default */
|
||||
@apply [&:not(.select-item-right)]:select-item-left;
|
||||
}
|
||||
|
||||
.select-item-check {
|
||||
@apply absolute flex h-4 w-4 items-center justify-center;
|
||||
|
||||
/* default */
|
||||
@apply [&:not(.select-item-check-right)]:select-item-check-left;
|
||||
}
|
||||
|
||||
.select-separator {
|
||||
@apply -mx-1 my-1 h-px bg-[var(--select-border)];
|
||||
}
|
||||
|
||||
.select-tag {
|
||||
@apply mr-2.5 !gap-0;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.sheet-overlay {
|
||||
@apply data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-dim fixed inset-0 z-50;
|
||||
}
|
||||
|
||||
.sheet {
|
||||
--sheet-border: var(--border-neutral-tertiary);
|
||||
|
||||
@apply data-[state=open]:animate-in data-[state=closed]:animate-out bg-neutral-primary text-neutral-primary fixed z-50 flex flex-col space-y-5 border-[var(--sheet-border)] p-5 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500;
|
||||
|
||||
/* default */
|
||||
@apply sheet-radius-small [&:not([class*=sheet-side])]:sheet-side-right;
|
||||
}
|
||||
|
||||
.sheet-close {
|
||||
@apply !absolute right-2 top-2 !m-0;
|
||||
}
|
||||
|
||||
.sheet-header {
|
||||
@apply flex flex-shrink-0 flex-col gap-1 text-left;
|
||||
}
|
||||
|
||||
.sheet-footer {
|
||||
@apply flex flex-shrink-0 justify-end gap-2;
|
||||
}
|
||||
|
||||
.sheet-icon {
|
||||
@apply flex p-1;
|
||||
}
|
||||
|
||||
.sheet-title {
|
||||
@apply text-xlarge-strong;
|
||||
}
|
||||
|
||||
.sheet-description {
|
||||
@apply text-neutral-secondary text-base-normal;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.spinner {
|
||||
@apply inline-flex animate-[spin_1s_cubic-bezier(0.4,0,0.2,1)_infinite];
|
||||
}
|
||||
|
||||
.spinner-small {
|
||||
@apply w-4 h-4;
|
||||
}
|
||||
|
||||
.spinner-medium {
|
||||
@apply w-5 h-5;
|
||||
}
|
||||
|
||||
.spinner-large {
|
||||
@apply w-6 h-6;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath opacity='0.25' fill-rule='evenodd' clip-rule='evenodd' d='M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22ZM12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24Z' fill='black'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 2C6.47715 2 2 6.47715 2 12C2 12.5523 1.55228 13 1 13C0.447715 13 0 12.5523 0 12C0 5.37258 5.37258 0 12 0C18.6274 0 24 5.37258 24 12C24 12.5523 23.5523 13 23 13C22.4477 13 22 12.5523 22 12C22 6.47715 17.5228 2 12 2Z' fill='black'/%3E%3C/svg%3E%0A");
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100%;
|
||||
background-color: currentcolor;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.switch {
|
||||
--switch-fg: var(--fg-neutral-primary);
|
||||
--switch-handle-bg: var(--bg-neutral-primary);
|
||||
--switch-bg: var(--bg-neutral-inverse);
|
||||
--switch-bg-blue: var(--bg-tint-blue-bold);
|
||||
--switch-bg-orange: var(--bg-tint-orange-bold);
|
||||
--switch-bg-red: var(--bg-tint-red-bold);
|
||||
--switch-bg-green: var(--bg-tint-green-bold);
|
||||
--switch-bg-off: var(--bg-neutral-secondary);
|
||||
|
||||
@apply text-base-normal relative inline-flex h-6 cursor-pointer items-center gap-2 text-[var(--switch-fg)] before:box-content before:flex before:h-5 before:w-10 before:rounded-full before:p-0.5 before:transition-colors disabled:cursor-not-allowed disabled:opacity-50 data-[state=unchecked]:before:bg-[var(--switch-bg-off)];
|
||||
|
||||
/* default */
|
||||
@apply switch-default;
|
||||
}
|
||||
|
||||
.switch-thumb {
|
||||
@apply absolute left-0.5 top-0.5 flex h-5 w-5 translate-x-0 rounded-full bg-[var(--switch-handle-bg)] transition-transform data-[state=checked]:translate-x-full;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.table {
|
||||
--table-header-fg: var(--fg-neutral-tertiary);
|
||||
--table-cell-fg: var(--fg-neutral-primary);
|
||||
--table-border: var(--border-neutral-tertiary);
|
||||
--table-bg: var(--bg-neutral-primary);
|
||||
--table-bg-hover: var(--bg-neutral-tertiary);
|
||||
|
||||
@apply w-full caption-bottom border-[var(--table-border)];
|
||||
}
|
||||
|
||||
.table-header {
|
||||
@apply [&_th]:border-b [&_th]:border-b-[var(--table-border)];
|
||||
}
|
||||
|
||||
.table-body {
|
||||
@apply [&_td]:border-b [&_td]:border-b-[var(--table-border)] [&_tr:last-child_td]:border-0;
|
||||
}
|
||||
|
||||
.table-footer {
|
||||
@apply border-t border-t-[var(--table-border)] bg-[var(--table-bg-hover)] [&>tr]:last:border-b-0;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
@apply border-b border-b-[var(--table-border)] transition-colors hover:bg-[var(--table-bg-hover)] data-[state=selected]:bg-[var(--table-bg-hover)];
|
||||
}
|
||||
|
||||
.table-head {
|
||||
@apply text-base-normal h-12 px-4 align-middle font-normal text-[var(--table-header-fg)] [&:has([role=checkbox])]:pr-0 [&>.badge]:mr-1 [&>svg]:mr-1 [&>svg]:text-[var(--table-header-fg)];
|
||||
|
||||
/* default */
|
||||
@apply table-head-left;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
@apply text-base-normal px-4 py-3 align-middle text-[var(--table-cell-fg)] [&:has([role=checkbox])]:pr-0 [&>.badge]:mr-1 [&>svg]:text-[var(--table-cell-fg)];
|
||||
|
||||
/* default */
|
||||
@apply table-cell-left;
|
||||
}
|
||||
|
||||
.table-caption {
|
||||
@apply text-small-normal mt-1 text-[--table-header-fg];
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.tabs {
|
||||
--tabs-fg-unselected: var(--fg-neutral-tertiary);
|
||||
--tabs-fg-selected: var(--fg-neutral-primary);
|
||||
--tabs-bg-unselected: var(--bg-neutral-tertiary);
|
||||
--tabs-bg-selected: var(--bg-neutral-primary);
|
||||
}
|
||||
|
||||
.tabs-list {
|
||||
@apply rounded-8 h-9.5 inline-flex items-center justify-center bg-[var(--tabs-bg-unselected)] p-1 text-[var(--tabs-fg-selected)];
|
||||
}
|
||||
|
||||
.tabs-trigger {
|
||||
@apply rounded-6 data-[state=active]:shadow-default text-base-normal inline-flex items-center justify-center whitespace-nowrap px-3 py-1 font-medium text-[var(--tabs-fg-unselected)] transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-[var(--tabs-bg-selected)] data-[state=active]:text-[var(--tabs-fg-selected)] [&>svg]:h-4 [&>svg]:w-4;
|
||||
}
|
||||
|
||||
.tabs-content {
|
||||
@apply mt-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.tag {
|
||||
--tag-primary-fg: var(--fg-neutral-inverse);
|
||||
--tag-secondary-fg: var(--fg-neutral-primary);
|
||||
--tag-destructive-fg: var(--fg-neutral-inverse);
|
||||
--tag-outline-fg: var(--tint, var(--fg-neutral-primary));
|
||||
--tag-outline-border: var(--border-neutral-tertiary);
|
||||
--tag-primary-bg: var(--tint, var(--bg-neutral-inverse));
|
||||
--tag-secondary-bg: var(--bg-neutral-tertiary);
|
||||
--tag-destructive-bg: var(--bg-tint-red-bold);
|
||||
--tag-outline-bg: var(--bg-neutral-transparent);
|
||||
--tag-outline-bg-hover: var(--tint-subtle, var(--bg-neutral-secondary));
|
||||
|
||||
@apply inline-flex items-center whitespace-nowrap hover:opacity-80 [&>svg]:flex-shrink-0;
|
||||
|
||||
/* default */
|
||||
@apply tag-small tag-radius-medium tag-primary;
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
const plugin = require("tailwindcss/plugin");
|
||||
|
||||
function filterDefault(values) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(values).filter(([key]) => key !== "DEFAULT"),
|
||||
);
|
||||
}
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: "class",
|
||||
content: [{ raw: "" }],
|
||||
theme: require("../theme"),
|
||||
plugins: [
|
||||
plugin(
|
||||
({ addUtilities, matchUtilities, theme }) => {
|
||||
addUtilities({
|
||||
"@keyframes enter": theme("keyframes.enter"),
|
||||
"@keyframes exit": theme("keyframes.exit"),
|
||||
".animate-in": {
|
||||
animationName: "enter",
|
||||
animationDuration: theme("animationDuration.DEFAULT"),
|
||||
"--tw-enter-opacity": "initial",
|
||||
"--tw-enter-scale": "initial",
|
||||
"--tw-enter-rotate": "initial",
|
||||
"--tw-enter-translate-x": "initial",
|
||||
"--tw-enter-translate-y": "initial",
|
||||
},
|
||||
".animate-out": {
|
||||
animationName: "exit",
|
||||
animationDuration: theme("animationDuration.DEFAULT"),
|
||||
"--tw-exit-opacity": "initial",
|
||||
"--tw-exit-scale": "initial",
|
||||
"--tw-exit-rotate": "initial",
|
||||
"--tw-exit-translate-x": "initial",
|
||||
"--tw-exit-translate-y": "initial",
|
||||
},
|
||||
});
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"fade-in": (value) => ({ "--tw-enter-opacity": value }),
|
||||
"fade-out": (value) => ({ "--tw-exit-opacity": value }),
|
||||
},
|
||||
{ values: theme("animationOpacity") },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"zoom-in": (value) => ({ "--tw-enter-scale": value }),
|
||||
"zoom-out": (value) => ({ "--tw-exit-scale": value }),
|
||||
},
|
||||
{ values: theme("animationScale") },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"spin-in": (value) => ({ "--tw-enter-rotate": value }),
|
||||
"spin-out": (value) => ({ "--tw-exit-rotate": value }),
|
||||
},
|
||||
{ values: theme("animationRotate") },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"slide-in-from-top": (value) => ({
|
||||
"--tw-enter-translate-y": `-${value}`,
|
||||
}),
|
||||
"slide-in-from-bottom": (value) => ({
|
||||
"--tw-enter-translate-y": value,
|
||||
}),
|
||||
"slide-in-from-left": (value) => ({
|
||||
"--tw-enter-translate-x": `-${value}`,
|
||||
}),
|
||||
"slide-in-from-right": (value) => ({
|
||||
"--tw-enter-translate-x": value,
|
||||
}),
|
||||
"slide-out-to-top": (value) => ({
|
||||
"--tw-exit-translate-y": `-${value}`,
|
||||
}),
|
||||
"slide-out-to-bottom": (value) => ({
|
||||
"--tw-exit-translate-y": value,
|
||||
}),
|
||||
"slide-out-to-left": (value) => ({
|
||||
"--tw-exit-translate-x": `-${value}`,
|
||||
}),
|
||||
"slide-out-to-right": (value) => ({
|
||||
"--tw-exit-translate-x": value,
|
||||
}),
|
||||
},
|
||||
{ values: theme("animationTranslate") },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{ duration: (value) => ({ animationDuration: value }) },
|
||||
{ values: filterDefault(theme("animationDuration")) },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{ delay: (value) => ({ animationDelay: value }) },
|
||||
{ values: theme("animationDelay") },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{ ease: (value) => ({ animationTimingFunction: value }) },
|
||||
{ values: filterDefault(theme("animationTimingFunction")) },
|
||||
);
|
||||
|
||||
addUtilities({
|
||||
".running": { animationPlayState: "running" },
|
||||
".paused": { animationPlayState: "paused" },
|
||||
});
|
||||
|
||||
matchUtilities(
|
||||
{ "fill-mode": (value) => ({ animationFillMode: value }) },
|
||||
{ values: theme("animationFillMode") },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{ direction: (value) => ({ animationDirection: value }) },
|
||||
{ values: theme("animationDirection") },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{ repeat: (value) => ({ animationIterationCount: value }) },
|
||||
{ values: theme("animationRepeat") },
|
||||
);
|
||||
},
|
||||
{
|
||||
theme: {
|
||||
extend: {
|
||||
animationDelay: ({ theme }) => ({
|
||||
...theme("transitionDelay"),
|
||||
}),
|
||||
animationDuration: ({ theme }) => ({
|
||||
0: "0ms",
|
||||
...theme("transitionDuration"),
|
||||
}),
|
||||
animationTimingFunction: ({ theme }) => ({
|
||||
...theme("transitionTimingFunction"),
|
||||
}),
|
||||
animationFillMode: {
|
||||
none: "none",
|
||||
forwards: "forwards",
|
||||
backwards: "backwards",
|
||||
both: "both",
|
||||
},
|
||||
animationDirection: {
|
||||
normal: "normal",
|
||||
reverse: "reverse",
|
||||
alternate: "alternate",
|
||||
"alternate-reverse": "alternate-reverse",
|
||||
},
|
||||
animationOpacity: ({ theme }) => ({
|
||||
DEFAULT: 0,
|
||||
...theme("opacity"),
|
||||
}),
|
||||
animationTranslate: ({ theme }) => ({
|
||||
DEFAULT: "100%",
|
||||
...theme("translate"),
|
||||
}),
|
||||
animationScale: ({ theme }) => ({
|
||||
DEFAULT: 0,
|
||||
...theme("scale"),
|
||||
}),
|
||||
animationRotate: ({ theme }) => ({
|
||||
DEFAULT: "30deg",
|
||||
...theme("rotate"),
|
||||
}),
|
||||
animationRepeat: {
|
||||
0: "0",
|
||||
1: "1",
|
||||
infinite: "infinite",
|
||||
},
|
||||
animation: {
|
||||
in: "enter 0.15s ease-in-out",
|
||||
out: "exit 0.15s ease-in-out",
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
keyframes: {
|
||||
enter: {
|
||||
from: {
|
||||
opacity: "var(--tw-enter-opacity, 1)",
|
||||
transform:
|
||||
"translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))",
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
to: {
|
||||
opacity: "var(--tw-exit-opacity, 1)",
|
||||
transform:
|
||||
"translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))",
|
||||
},
|
||||
},
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
plugin(({ addBase, addUtilities }) => {
|
||||
addBase(require("../../dist/base"));
|
||||
addUtilities(require("../../dist/utilities"));
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.textarea {
|
||||
--textarea-fg: var(--fg-neutral-tertiary);
|
||||
--textarea-fg-typing: var(--fg-neutral-primary);
|
||||
--textarea-fg-filled: var(--fg-neutral-primary);
|
||||
--textarea-border: var(--border-neutral-tertiary);
|
||||
--textarea-border-typing: var(--tint, var(--border-neutral-primary));
|
||||
--textarea-border-filled: var(--border-neutral-tertiary);
|
||||
--textarea-bg: var(--bg-neutral-primary);
|
||||
--textarea-bg-typing: var(--bg-neutral-primary);
|
||||
--textarea-bg-filled: var(--bg-neutral-primary);
|
||||
|
||||
@apply text-[var(--textarea-fg-filled)] placeholder:text-[var(--textarea-fg)] placeholder-shown:text-[var(--textarea-fg)] focus-visible:text-[var(--textarea-fg-typing)];
|
||||
@apply border-[var(--textarea-border-filled)] placeholder-shown:border-[var(--textarea-border)] focus-visible:border-[var(--textarea-border-typing)];
|
||||
@apply bg-[var(--textarea-bg-filled)] placeholder-shown:bg-[var(--textarea-bg)] focus-visible:bg-[var(--textarea-bg-typing)];
|
||||
|
||||
@apply rounded-8 text-base-normal flex min-h-20 w-full border px-3 py-2 outline-none;
|
||||
@apply disabled:cursor-not-allowed disabled:opacity-50;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.toaster {
|
||||
}
|
||||
|
||||
.toast {
|
||||
--toast-fg: var(--fg-neutral-primary);
|
||||
--toast-fg-secondary: var(--fg-neutral-secondary);
|
||||
--toast-fg-warning: var(--fg-tint-orange);
|
||||
--toast-fg-success: var(--fg-tint-green);
|
||||
--toast-fg-error: var(--fg-tint-red);
|
||||
--toast-fg-informative: var(--fg-tint-blue);
|
||||
--toast-border: var(--border-neutral-tertiary);
|
||||
--toast-border-warning: var(--border-tint-orange);
|
||||
--toast-border-success: var(--border-tint-green);
|
||||
--toast-border-error: var(--border-tint-red);
|
||||
--toast-border-informative: var(--border-tint-blue);
|
||||
--toast-bg: var(--bg-neutral-primary);
|
||||
|
||||
@apply !absolute !left-0 inline-flex min-w-[356px] items-center space-x-2 border bg-[var(--toast-bg)] px-5 py-4;
|
||||
|
||||
/* default */
|
||||
@apply toast-radius-medium toast-default;
|
||||
}
|
||||
|
||||
.toast-content {
|
||||
@apply flex flex-1 flex-col items-start justify-center;
|
||||
}
|
||||
|
||||
.toast-title {
|
||||
@apply text-title-h5 flex flex-1 flex-col text-[var(--toast-fg)];
|
||||
}
|
||||
|
||||
.toast-description {
|
||||
@apply text-base-normal text-[var(--toast-fg-secondary)];
|
||||
}
|
||||
|
||||
.toast-button {
|
||||
@apply flex-shrink-0;
|
||||
}
|
||||
|
||||
.toast-close {
|
||||
@apply order-1 flex-shrink-0 !px-0;
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
@apply relative mb-auto box-content flex h-5 w-5 flex-shrink-0 p-1;
|
||||
|
||||
/* default */
|
||||
@apply toast-icon-default;
|
||||
}
|
||||
|
||||
.toast-loader {
|
||||
@apply flex scale-100;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
.toggle-group {
|
||||
--toggle-group-fg: var(--fg-neutral-primary);
|
||||
--toggle-group-fg-hover: var(--fg-neutral-primary);
|
||||
--toggle-group-fg-pressed: var(--tint, var(--fg-neutral-primary));
|
||||
--toggle-group-border: var(--border-neutral-tertiary);
|
||||
--toggle-group-border-hover: var(--border-neutral-tertiary);
|
||||
--toggle-group-border-pressed: var(--border-neutral-tertiary);
|
||||
--toggle-group-bg: var(--bg-neutral-primary);
|
||||
--toggle-group-bg-hover: var(--bg-neutral-tertiary);
|
||||
--toggle-group-bg-pressed: var(--bg-neutral-tertiary);
|
||||
|
||||
@apply relative inline-flex items-center justify-center;
|
||||
}
|
||||
|
||||
.toggle-group-item {
|
||||
@apply inline-flex cursor-pointer items-center justify-center gap-1 border border-[var(--toggle-group-border)] bg-[var(--toggle-group-bg)] text-[var(--toggle-group-fg)] transition-colors disabled:cursor-not-allowed disabled:opacity-50 data-[state=on]:border-[var(--toggle-group-border-pressed)] data-[state=on]:bg-[var(--toggle-group-bg-pressed)] data-[state=on]:text-[var(--toggle-group-fg-pressed)] [&:not(:disabled)]:hover:border-[var(--toggle-group-border-hover)] [&:not(:disabled)]:hover:bg-[var(--toggle-group-bg-hover)] [&:not(:disabled)]:hover:text-[var(--toggle-group-fg-hover)] data-[state=on]:[&:not(:disabled)]:hover:border-[var(--toggle-group-border-hover)] data-[state=on]:[&:not(:disabled)]:hover:bg-[var(--toggle-group-bg-hover)] data-[state=on]:[&:not(:disabled)]:hover:text-[var(--toggle-group-fg-hover)];
|
||||
|
||||
/* default */
|
||||
@apply toggle-group-item-small toggle-group-item-radius-medium;
|
||||
|
||||
~ .toggle-group-item {
|
||||
@apply -ml-px;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-group-icon {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
|
||||
.toggle-group-input {
|
||||
@apply absolute m-0 block h-0 w-0 overflow-hidden p-0;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user