mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-26 22:24:42 +08:00
Initial commit from Create Next App
This commit is contained in:
64
src/components/links/ArrowLink.tsx
Normal file
64
src/components/links/ArrowLink.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
146
src/components/links/ButtonLink.tsx
Normal file
146
src/components/links/ButtonLink.tsx
Normal 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;
|
||||
97
src/components/links/IconLink.tsx
Normal file
97
src/components/links/IconLink.tsx
Normal 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;
|
||||
43
src/components/links/PrimaryLink.tsx
Normal file
43
src/components/links/PrimaryLink.tsx
Normal 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;
|
||||
28
src/components/links/UnderlineLink.tsx
Normal file
28
src/components/links/UnderlineLink.tsx
Normal 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;
|
||||
50
src/components/links/UnstyledLink.tsx
Normal file
50
src/components/links/UnstyledLink.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user