Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/assets/icons/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/components/ui/Chip/ColorChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { HexColor } from '@/constants/colors';
import { cn } from '@/utils/helper';
import { HTMLAttributes } from 'react';

interface ColorChipProps extends HTMLAttributes<HTMLSpanElement> {
color: HexColor;
}

export default function ColorChip({ color, className, ...props }: ColorChipProps) {
return (
<span className={cn('inline-flex h-[30px] w-[30px] rounded-full leading-none', className)} style={{ backgroundColor: color }} {...props}>
<span className='sr-only'>{color}</span>
</span>
);
}
45 changes: 45 additions & 0 deletions src/components/ui/Chip/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Image from 'next/image';
import { DEFAULT_COLORS } from '@/constants/colors';
import check_icon from '@/assets/icons/check.svg';
import ColorChip from './ColorChip';
import { cn } from '@/utils/helper';

/**
* input name을 통한 radio group으로 구성된 컬러 피커 입니다.
* Controller를 사용하여 `react-hook-form`과 `ColorPicker` 컴포넌트를 연결하시면 됩니다.
* (name은 zod schema key값을 내려주면 됩니다.)
*
* @example
* <Controller
* control={control}
* name='color'
* render={({ field }) => (
* <ColorPicker
* name='color'
* selected={field.value}
* onChange={(value) => field.onChange(value)}
* />
* )}
* />
*/

interface ColorPickerProps {
name: string;
selected: string;
onChange: (value: string) => void;
className?: string;
}

export default function ColorPicker({ name = 'color', selected, onChange, className }: ColorPickerProps) {
return (
<div className={cn('flex flex-wrap gap-2 leading-none', className)}>
{DEFAULT_COLORS.map((color) => (
<label key={color} className='relative cursor-pointer'>
<input type='radio' className='peer' name={name} value={color} hidden checked={selected === color} onChange={(e) => onChange(e.target.value)} />
<ColorChip color={color} />
<Image src={check_icon} alt='선택됨' className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity peer-checked:opacity-100' />
</label>
))}
</div>
);
}
19 changes: 19 additions & 0 deletions src/components/ui/Chip/NumberChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cn } from '@/utils/helper';
import { HTMLAttributes } from 'react';

interface NumberChipProps extends HTMLAttributes<HTMLSpanElement> {
label: number;
max?: number;
}

const DEFAULT_MAX_NUMBER = 99;

export default function NumberChip({ label, max = DEFAULT_MAX_NUMBER, className, ...props }: NumberChipProps) {
const safeParseMaxNumber = max <= 0 || !Number.isInteger(max) ? DEFAULT_MAX_NUMBER : max;

return (
<span className={cn('inline-flex h-5 min-w-5 items-center justify-center whitespace-nowrap rounded bg-gray-20 px-1 text-xs leading-none text-gray-50', className)} {...props}>
{label > safeParseMaxNumber ? `${safeParseMaxNumber}+` : label}
</span>
);
}
21 changes: 21 additions & 0 deletions src/components/ui/Chip/RoundChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { cn } from '@/utils/helper';
import { HTMLAttributes } from 'react';

interface RoundChipProps extends HTMLAttributes<HTMLSpanElement> {
label: string;
}

export default function RoundChip({ label, className, ...props }: RoundChipProps) {
return (
<span
className={cn(
'inline-grid h-[26px] grid-flow-col items-center justify-center gap-[6px] rounded-full bg-violet-10 px-2 text-xs leading-none text-violet-20 before:h-[6px] before:w-[6px] before:rounded-full before:bg-violet-20 md:h-8 md:px-[10px] md:text-md',
className,
)}
title={label}
{...props}
>
<span className='truncate'>{label}</span>
</span>
);
}
32 changes: 32 additions & 0 deletions src/components/ui/Chip/TagChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { HTMLAttributes } from 'react';
import { DEFAULT_COLORS } from '@/constants/colors';
import { cn, getColorByString } from '@/utils/helper';

/**
* 하나의 color 값으로 배경색과 폰트색상에 적용합니다.
*
* 받은 color가 #FFCC00일경우
*
* font color => #FFCCOO
* background color => #FFCCOO30 (30% opacity)
*/

const BACKGROUND_ALPHA = 30;

interface TagChipProps extends HTMLAttributes<HTMLSpanElement> {
label: string;
}

export default function TagChip({ label, className, ...props }: TagChipProps) {
const colorCode = getColorByString(label, DEFAULT_COLORS);

return (
<span
className={cn('inline-flex h-[26px] items-center justify-center whitespace-nowrap rounded-[4px] px-[6px] text-xs leading-none md:h-7 md:text-md', className)}
style={{ color: colorCode, backgroundColor: `${colorCode}${BACKGROUND_ALPHA}` }}
{...props}
>
{label}
</span>
);
}
2 changes: 1 addition & 1 deletion src/constants/colors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
type HexColor = `#${string}`;
export type HexColor = `#${string}`;

export const DEFAULT_COLORS: HexColor[] = ['#7AC555', '#760DDE', '#FFA500', '#76A5EA', '#E876EA'];