Initial commit from Create Next App

This commit is contained in:
shinya
2025-06-17 13:15:54 +08:00
commit 2e989e5e9b
77 changed files with 13025 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import UnderlineLink from '@/components/links/UnderlineLink';
import { UnstyledLinkProps } from '@/components/links/UnstyledLink';
type ArrowLinkProps<C extends React.ElementType> = {
as?: C;
direction?: 'left' | 'right';
} & UnstyledLinkProps &
React.ComponentProps<C>;
export default function ArrowLink<C extends React.ElementType>({
children,
className,
direction = 'right',
as,
...rest
}: ArrowLinkProps<C>) {
const Component = as || UnderlineLink;
return (
<Component
{...rest}
className={cn(
'group gap-[0.25em]',
direction === 'left' && 'flex-row-reverse',
className
)}
>
<span>{children}</span>
<svg
viewBox='0 0 16 16'
height='1em'
width='1em'
fill='none'
xmlns='http://www.w3.org/2000/svg'
className={cn(
'relative',
'transition-transform duration-200',
direction === 'right' ? 'motion-safe:-translate-x-1' : 'rotate-180',
'group-hover:translate-x-0'
)}
>
<path
fill='currentColor'
d='M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z'
/>
<path
stroke='currentColor'
d='M1.75 8H11'
strokeWidth='1.5'
strokeLinecap='round'
className={cn(
'origin-left transition-all duration-200',
'opacity-0 motion-safe:-translate-x-1',
'group-hover:translate-x-0 group-hover:opacity-100'
)}
/>
</svg>
</Component>
);
}

View File

@@ -0,0 +1,146 @@
import { LucideIcon } from 'lucide-react';
import * as React from 'react';
import { IconType } from 'react-icons';
import { cn } from '@/lib/utils';
import UnstyledLink, {
UnstyledLinkProps,
} from '@/components/links/UnstyledLink';
const ButtonLinkVariant = [
'primary',
'outline',
'ghost',
'light',
'dark',
] as const;
const ButtonLinkSize = ['sm', 'base'] as const;
type ButtonLinkProps = {
isDarkBg?: boolean;
variant?: (typeof ButtonLinkVariant)[number];
size?: (typeof ButtonLinkSize)[number];
leftIcon?: IconType | LucideIcon;
rightIcon?: IconType | LucideIcon;
classNames?: {
leftIcon?: string;
rightIcon?: string;
};
} & UnstyledLinkProps;
const ButtonLink = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
(
{
children,
className,
variant = 'primary',
size = 'base',
isDarkBg = false,
leftIcon: LeftIcon,
rightIcon: RightIcon,
classNames,
...rest
},
ref
) => {
return (
<UnstyledLink
ref={ref}
{...rest}
className={cn(
'inline-flex items-center rounded font-medium',
'focus-visible:ring-primary-500 focus:outline-none focus-visible:ring',
'shadow-sm',
'transition-colors duration-75',
//#region //*=========== Size ===========
[
size === 'base' && ['px-3 py-1.5', 'text-sm md:text-base'],
size === 'sm' && ['px-2 py-1', 'text-xs md:text-sm'],
],
//#endregion //*======== Size ===========
//#region //*=========== Variants ===========
[
variant === 'primary' && [
'bg-primary-500 text-white',
'border-primary-600 border',
'hover:bg-primary-600 hover:text-white',
'active:bg-primary-700',
'disabled:bg-primary-700',
],
variant === 'outline' && [
'text-primary-500',
'border-primary-500 border',
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
isDarkBg &&
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
],
variant === 'ghost' && [
'text-primary-500',
'shadow-none',
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
isDarkBg &&
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
],
variant === 'light' && [
'bg-white text-gray-700',
'border border-gray-300',
'hover:text-dark hover:bg-gray-100',
'active:bg-white/80 disabled:bg-gray-200',
],
variant === 'dark' && [
'bg-gray-900 text-white',
'border border-gray-600',
'hover:bg-gray-800 active:bg-gray-700 disabled:bg-gray-700',
],
],
//#endregion //*======== Variants ===========
'disabled:cursor-not-allowed',
className
)}
>
{LeftIcon && (
<div
className={cn([
size === 'base' && 'mr-1',
size === 'sm' && 'mr-1.5',
])}
>
<LeftIcon
size='1em'
className={cn(
[
size === 'base' && 'md:text-md text-md',
size === 'sm' && 'md:text-md text-sm',
],
classNames?.leftIcon
)}
/>
</div>
)}
{children}
{RightIcon && (
<div
className={cn([
size === 'base' && 'ml-1',
size === 'sm' && 'ml-1.5',
])}
>
<RightIcon
size='1em'
className={cn(
[
size === 'base' && 'text-md md:text-md',
size === 'sm' && 'md:text-md text-sm',
],
classNames?.rightIcon
)}
/>
</div>
)}
</UnstyledLink>
);
}
);
export default ButtonLink;

View File

@@ -0,0 +1,97 @@
import { LucideIcon } from 'lucide-react';
import * as React from 'react';
import { IconType } from 'react-icons';
import { cn } from '@/lib/utils';
import UnstyledLink, {
UnstyledLinkProps,
} from '@/components/links/UnstyledLink';
const IconLinkVariant = [
'primary',
'outline',
'ghost',
'light',
'dark',
] as const;
type IconLinkProps = {
isDarkBg?: boolean;
variant?: (typeof IconLinkVariant)[number];
icon?: IconType | LucideIcon;
classNames?: {
icon?: string;
};
} & Omit<UnstyledLinkProps, 'children'>;
const IconLink = React.forwardRef<HTMLAnchorElement, IconLinkProps>(
(
{
className,
icon: Icon,
variant = 'outline',
isDarkBg = false,
classNames,
...rest
},
ref
) => {
return (
<UnstyledLink
ref={ref}
type='button'
className={cn(
'inline-flex items-center justify-center rounded font-medium',
'focus-visible:ring-primary-500 focus:outline-none focus-visible:ring',
'shadow-sm',
'transition-colors duration-75',
'min-h-[28px] min-w-[28px] p-1 md:min-h-[34px] md:min-w-[34px] md:p-2',
//#region //*=========== Variants ===========
[
variant === 'primary' && [
'bg-primary-500 text-white',
'border-primary-600 border',
'hover:bg-primary-600 hover:text-white',
'active:bg-primary-700',
'disabled:bg-primary-700',
],
variant === 'outline' && [
'text-primary-500',
'border-primary-500 border',
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
isDarkBg &&
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
],
variant === 'ghost' && [
'text-primary-500',
'shadow-none',
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
isDarkBg &&
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
],
variant === 'light' && [
'bg-white text-gray-700',
'border border-gray-300',
'hover:text-dark hover:bg-gray-100',
'active:bg-white/80 disabled:bg-gray-200',
],
variant === 'dark' && [
'bg-gray-900 text-white',
'border border-gray-600',
'hover:bg-gray-800 active:bg-gray-700 disabled:bg-gray-700',
],
],
//#endregion //*======== Variants ===========
'disabled:cursor-not-allowed',
className
)}
{...rest}
>
{Icon && <Icon size='1em' className={cn(classNames?.icon)} />}
</UnstyledLink>
);
}
);
export default IconLink;

View File

@@ -0,0 +1,43 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import UnstyledLink, {
UnstyledLinkProps,
} from '@/components/links/UnstyledLink';
const PrimaryLinkVariant = ['primary', 'basic'] as const;
type PrimaryLinkProps = {
variant?: (typeof PrimaryLinkVariant)[number];
} & UnstyledLinkProps;
const PrimaryLink = React.forwardRef<HTMLAnchorElement, PrimaryLinkProps>(
({ className, children, variant = 'primary', ...rest }, ref) => {
return (
<UnstyledLink
ref={ref}
{...rest}
className={cn(
'inline-flex items-center',
'focus-visible:ring-primary-500 focus:outline-none focus-visible:rounded focus-visible:ring focus-visible:ring-offset-2',
'font-medium',
//#region //*=========== Variant ===========
variant === 'primary' && [
'text-primary-500 hover:text-primary-600 active:text-primary-700',
'disabled:text-primary-200',
],
variant === 'basic' && [
'text-black hover:text-gray-600 active:text-gray-800',
'disabled:text-gray-300',
],
//#endregion //*======== Variant ===========
className
)}
>
{children}
</UnstyledLink>
);
}
);
export default PrimaryLink;

View File

@@ -0,0 +1,28 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import UnstyledLink, {
UnstyledLinkProps,
} from '@/components/links/UnstyledLink';
const UnderlineLink = React.forwardRef<HTMLAnchorElement, UnstyledLinkProps>(
({ children, className, ...rest }, ref) => {
return (
<UnstyledLink
ref={ref}
{...rest}
className={cn(
'animated-underline custom-link inline-flex items-center font-medium',
'focus-visible:ring-primary-500 focus:outline-none focus-visible:rounded focus-visible:ring focus-visible:ring-offset-2',
'border-dark border-b border-dotted hover:border-black/0',
className
)}
>
{children}
</UnstyledLink>
);
}
);
export default UnderlineLink;

View File

@@ -0,0 +1,50 @@
import Link, { LinkProps } from 'next/link';
import * as React from 'react';
import { cn } from '@/lib/utils';
export type UnstyledLinkProps = {
href: string;
children: React.ReactNode;
openNewTab?: boolean;
className?: string;
nextLinkProps?: Omit<LinkProps, 'href'>;
} & React.ComponentPropsWithRef<'a'>;
const UnstyledLink = React.forwardRef<HTMLAnchorElement, UnstyledLinkProps>(
({ children, href, openNewTab, className, nextLinkProps, ...rest }, ref) => {
const isNewTab =
openNewTab !== undefined
? openNewTab
: href && !href.startsWith('/') && !href.startsWith('#');
if (!isNewTab) {
return (
<Link
href={href}
ref={ref}
className={className}
{...rest}
{...nextLinkProps}
>
{children}
</Link>
);
}
return (
<a
ref={ref}
target='_blank'
rel='noopener noreferrer'
href={href}
{...rest}
className={cn('cursor-newtab', className)}
>
{children}
</a>
);
}
);
export default UnstyledLink;