@remember-web/primitive 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Chips/Chip/const.cjs.js +107 -0
- package/dist/src/Chips/Chip/const.cjs.js.map +1 -0
- package/dist/src/Chips/Chip/const.d.ts +15 -0
- package/dist/src/Chips/Chip/const.d.ts.map +1 -0
- package/dist/src/Chips/Chip/const.esm.js +104 -0
- package/dist/src/Chips/Chip/const.esm.js.map +1 -0
- package/dist/src/Chips/Chip/index.cjs.js +57 -0
- package/dist/src/Chips/Chip/index.cjs.js.map +1 -0
- package/dist/src/Chips/Chip/index.d.ts +4 -0
- package/dist/src/Chips/Chip/index.d.ts.map +1 -0
- package/dist/src/Chips/Chip/index.esm.js +50 -0
- package/dist/src/Chips/Chip/index.esm.js.map +1 -0
- package/dist/src/Chips/Chip/styles.cjs.js +46 -0
- package/dist/src/Chips/Chip/styles.cjs.js.map +1 -0
- package/dist/src/Chips/Chip/styles.d.ts +5 -0
- package/dist/src/Chips/Chip/styles.d.ts.map +1 -0
- package/dist/src/Chips/Chip/styles.esm.js +36 -0
- package/dist/src/Chips/Chip/styles.esm.js.map +1 -0
- package/dist/src/Chips/Chip/typeGuard.cjs.js +31 -0
- package/dist/src/Chips/Chip/typeGuard.cjs.js.map +1 -0
- package/dist/src/Chips/Chip/typeGuard.d.ts +3 -0
- package/dist/src/Chips/Chip/typeGuard.d.ts.map +1 -0
- package/dist/src/Chips/Chip/typeGuard.esm.js +29 -0
- package/dist/src/Chips/Chip/typeGuard.esm.js.map +1 -0
- package/dist/src/Chips/Chip/types.d.ts +43 -0
- package/dist/src/Chips/Chip/types.d.ts.map +1 -0
- package/dist/src/Chips/index.d.ts +2 -0
- package/dist/src/Chips/index.d.ts.map +1 -0
- package/dist/src/Inputs/TextInput/index.cjs.js +8 -2
- package/dist/src/Inputs/TextInput/index.cjs.js.map +1 -1
- package/dist/src/Inputs/TextInput/index.d.ts.map +1 -1
- package/dist/src/Inputs/TextInput/index.esm.js +8 -2
- package/dist/src/Inputs/TextInput/index.esm.js.map +1 -1
- package/dist/src/Inputs/TextInput/styles.cjs.js +2 -2
- package/dist/src/Inputs/TextInput/styles.cjs.js.map +1 -1
- package/dist/src/Inputs/TextInput/styles.d.ts +1 -1
- package/dist/src/Inputs/TextInput/styles.esm.js +2 -2
- package/dist/src/Inputs/TextInput/styles.esm.js.map +1 -1
- package/dist/src/index.cjs.js +2 -0
- package/dist/src/index.cjs.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.esm.js +1 -0
- package/dist/src/index.esm.js.map +1 -1
- package/dist/src/stories/common.styles.d.ts +2 -1
- package/dist/src/stories/common.styles.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/Chips/Chip/Chip.stories.tsx +138 -0
- package/src/Chips/Chip/const.ts +115 -0
- package/src/Chips/Chip/index.tsx +51 -0
- package/src/Chips/Chip/styles.ts +80 -0
- package/src/Chips/Chip/typeGuard.ts +48 -0
- package/src/Chips/Chip/types.ts +61 -0
- package/src/Chips/index.ts +1 -0
- package/src/Inputs/TextInput/index.tsx +9 -2
- package/src/Inputs/TextInput/styles.ts +3 -3
- package/src/index.ts +1 -0
- package/src/stories/common.styles.tsx +10 -4
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { IconCloseS } from '@remember-web/icon';
|
|
2
|
+
import { type ForwardedRef, forwardRef } from 'react';
|
|
3
|
+
import type { PolymorphicComponent } from 'styled-components';
|
|
4
|
+
import { ButtonWrapper, StyledChip } from './styles';
|
|
5
|
+
import { ensureValidChipProps } from './typeGuard';
|
|
6
|
+
import type { ChipProps } from './types';
|
|
7
|
+
|
|
8
|
+
export const Chip = forwardRef(
|
|
9
|
+
(props: ChipProps, ref: ForwardedRef<HTMLDivElement | null>) => {
|
|
10
|
+
const {
|
|
11
|
+
size = 'medium',
|
|
12
|
+
variant = 'filled',
|
|
13
|
+
selected = false,
|
|
14
|
+
clickable = true,
|
|
15
|
+
...rest
|
|
16
|
+
} = props;
|
|
17
|
+
|
|
18
|
+
// NOTE: 개발 모드에서만 런타임 체크 수행
|
|
19
|
+
if (process.env.NODE_ENV === 'development') {
|
|
20
|
+
ensureValidChipProps(props);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<StyledChip
|
|
25
|
+
ref={ref}
|
|
26
|
+
aria-live="polite"
|
|
27
|
+
$size={size}
|
|
28
|
+
$variant={variant}
|
|
29
|
+
$selected={selected}
|
|
30
|
+
$clickable={clickable}
|
|
31
|
+
data-chip-disabled={props.disabled}
|
|
32
|
+
{...rest}
|
|
33
|
+
>
|
|
34
|
+
{props.leftIcon}
|
|
35
|
+
{props.children}
|
|
36
|
+
{props.onDelete && (
|
|
37
|
+
<ButtonWrapper
|
|
38
|
+
onClick={(e: React.MouseEvent | React.KeyboardEvent) => {
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
props.onDelete?.();
|
|
42
|
+
}}
|
|
43
|
+
className="delete-button"
|
|
44
|
+
>
|
|
45
|
+
{props.deleteIcon || <IconCloseS />}
|
|
46
|
+
</ButtonWrapper>
|
|
47
|
+
)}
|
|
48
|
+
</StyledChip>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
) as PolymorphicComponent<'web', ChipProps>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getTypographyStyles } from '@remember-web/mixin';
|
|
2
|
+
import styled, { css } from 'styled-components';
|
|
3
|
+
import { CHIP_SIZE_STYLE, CHIP_VARIANT_STYLE } from './const';
|
|
4
|
+
import type { ChipState, ChipStyleProps } from './types';
|
|
5
|
+
|
|
6
|
+
const getVariantStyles = (props: Partial<ChipStyleProps>) => {
|
|
7
|
+
const { $variant = 'filled', $selected, $clickable } = props;
|
|
8
|
+
const getStateStyle = (state: ChipState) => css`
|
|
9
|
+
background-color: ${CHIP_VARIANT_STYLE[$variant][state].backgroundColor};
|
|
10
|
+
color: ${CHIP_VARIANT_STYLE[$variant][state].color};
|
|
11
|
+
border: ${CHIP_VARIANT_STYLE[$variant][state].border || 'none'};
|
|
12
|
+
--left-icon-color: ${CHIP_VARIANT_STYLE[$variant][state].leftIconColor};
|
|
13
|
+
--delete-button-color: ${CHIP_VARIANT_STYLE[$variant][state].deleteButtonColor};
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
return css`
|
|
17
|
+
${getStateStyle('default')};
|
|
18
|
+
|
|
19
|
+
${
|
|
20
|
+
$clickable &&
|
|
21
|
+
css`
|
|
22
|
+
&:hover:not([data-chip-disabled='true']) {
|
|
23
|
+
${getStateStyle('hover')}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
${$selected ? '&' : '&:active'}:not([data-chip-disabled="true"]) {
|
|
27
|
+
${getStateStyle('selected')}
|
|
28
|
+
${$variant === 'outline' && 'border: none;'}
|
|
29
|
+
}
|
|
30
|
+
`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&[data-chip-disabled='true'] {
|
|
34
|
+
${getStateStyle('disabled')}
|
|
35
|
+
cursor: not-allowed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
& svg {
|
|
39
|
+
color: var(--left-icon-color);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.delete-button svg {
|
|
43
|
+
color: var(--delete-button-color);
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const StyledChip = styled.div<ChipStyleProps>`
|
|
49
|
+
${({ $size, $variant, $clickable = false, ...props }) => css`
|
|
50
|
+
box-sizing: border-box;
|
|
51
|
+
user-select: none;
|
|
52
|
+
cursor: ${$clickable ? 'pointer' : 'default'};
|
|
53
|
+
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
flex-shrink: 0;
|
|
58
|
+
gap: 4px;
|
|
59
|
+
|
|
60
|
+
width: fit-content;
|
|
61
|
+
border-radius: 50px;
|
|
62
|
+
text-align: center;
|
|
63
|
+
transition: all 0.2s ease-in;
|
|
64
|
+
|
|
65
|
+
height: ${CHIP_SIZE_STYLE[$size].height};
|
|
66
|
+
padding: ${CHIP_SIZE_STYLE[$size].padding};
|
|
67
|
+
|
|
68
|
+
${getTypographyStyles(CHIP_SIZE_STYLE[$size].typography)}
|
|
69
|
+
|
|
70
|
+
${getVariantStyles({ $variant, $clickable, ...props })}
|
|
71
|
+
`}
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
export const ButtonWrapper = styled.button`
|
|
75
|
+
all: unset;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
`;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ChipProps, ChipSize, ChipVariant } from './types';
|
|
2
|
+
|
|
3
|
+
export function ensureValidChipProps(props: ChipProps): props is ChipProps {
|
|
4
|
+
if (props.clickable === false && 'selected' in props) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
'오류: "clickable"이 false일 때는 "selected" 속성을 사용할 수 없습니다. ' +
|
|
7
|
+
'선택 가능한 Chip을 만들려면 "clickable"을 true로 설정하세요.'
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (props.onDelete === undefined && 'deleteIcon' in props) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'오류: "onDelete" 함수 없이 "deleteIcon"을 사용할 수 없습니다. ' +
|
|
14
|
+
'"deleteIcon"을 사용하려면 "onDelete" 함수를 함께 사용해야 합니다.'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!props.children) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'오류: Chip 컴포넌트에는 반드시 children이 있어야 합니다. '
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!props.size) {
|
|
25
|
+
throw new Error('오류: Chip 컴포넌트에는 반드시 size가 있어야 합니다. ');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!props.variant) {
|
|
29
|
+
throw new Error('오류: Chip 컴포넌트에는 반드시 variant가 있어야 합니다. ');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const validSizes: ChipSize[] = ['small', 'medium'];
|
|
33
|
+
if (props.size && !validSizes.includes(props.size)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`오류: 올바르지 않은 size 값입니다: "${props.size}". ` +
|
|
36
|
+
`유효한 size 값은 다음과 같습니다: ${validSizes.join(', ')}.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const validVariants: ChipVariant[] = ['filled', 'outline', 'dashedLine'];
|
|
41
|
+
if (props.variant && !validVariants.includes(props.variant)) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`오류: 올바르지 않은 variant 값입니다: "${props.variant}". ` +
|
|
44
|
+
`유효한 variant 값은 다음과 같습니다: ${validVariants.join(', ')}.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
// Props types -------------------------------------------------------
|
|
4
|
+
export type ChipSize = 'small' | 'medium';
|
|
5
|
+
|
|
6
|
+
export type ChipVariant = 'filled' | 'outline' | 'dashedLine';
|
|
7
|
+
|
|
8
|
+
type ClickableProps =
|
|
9
|
+
| {
|
|
10
|
+
clickable?: true;
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
clickable: false;
|
|
15
|
+
selected?: never;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type DeleteProps =
|
|
19
|
+
| {
|
|
20
|
+
onDelete: VoidFunction;
|
|
21
|
+
deleteIcon?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
onDelete?: never;
|
|
25
|
+
deleteIcon?: never;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type BaseChipProps = {
|
|
29
|
+
size: ChipSize;
|
|
30
|
+
variant: ChipVariant;
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
leftIcon?: React.ReactNode;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ChipProps = BaseChipProps &
|
|
37
|
+
ClickableProps &
|
|
38
|
+
DeleteProps &
|
|
39
|
+
ComponentProps<'div'>;
|
|
40
|
+
|
|
41
|
+
// Styling types -------------------------------------------------------
|
|
42
|
+
export type ChipState = 'default' | 'hover' | 'disabled' | 'selected';
|
|
43
|
+
|
|
44
|
+
export type ChipStyleProps = {
|
|
45
|
+
$size: ChipSize;
|
|
46
|
+
$variant: ChipVariant;
|
|
47
|
+
$clickable: boolean;
|
|
48
|
+
$selected: boolean;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
interface ChipStateStyle {
|
|
52
|
+
backgroundColor: string;
|
|
53
|
+
color: string;
|
|
54
|
+
leftIconColor: string;
|
|
55
|
+
deleteButtonColor: string;
|
|
56
|
+
border?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type ChipStateStyles = Record<ChipState, ChipStateStyle>;
|
|
60
|
+
|
|
61
|
+
export type ChipVariantStyleMap = Record<ChipVariant, ChipStateStyles>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Chip';
|
|
@@ -39,6 +39,13 @@ const TextInput: ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
|
|
|
39
39
|
props.hasTogglePasswordVisibilityButton &&
|
|
40
40
|
isVisiblePassword;
|
|
41
41
|
|
|
42
|
+
// NOTE: InputInner 컴포넌트에 전달되는 input native 속성에 커스텀 props인 hasTogglePasswordVisibilityButton가 전달되지 않도록 하는 처리
|
|
43
|
+
// type === 'password' 일 때에 hasTogglePasswordVisibilityButton 속성이 존재할 수 있음.
|
|
44
|
+
const inputInnerProps = {
|
|
45
|
+
...props,
|
|
46
|
+
hasTogglePasswordVisibilityButton: undefined,
|
|
47
|
+
};
|
|
48
|
+
|
|
42
49
|
return (
|
|
43
50
|
<InputWrapper $width={width}>
|
|
44
51
|
{label && (
|
|
@@ -49,7 +56,7 @@ const TextInput: ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
|
|
|
49
56
|
)}
|
|
50
57
|
<InputContainer
|
|
51
58
|
disabled={disabled}
|
|
52
|
-
hasError={!!errorMessage}
|
|
59
|
+
$hasError={!!errorMessage}
|
|
53
60
|
className={className}
|
|
54
61
|
>
|
|
55
62
|
{leftElement && <InputDecorator>{leftElement}</InputDecorator>}
|
|
@@ -57,7 +64,7 @@ const TextInput: ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
|
|
|
57
64
|
disabled={disabled}
|
|
58
65
|
id={inputId}
|
|
59
66
|
aria-labelledby={label ? labelId : undefined}
|
|
60
|
-
{...
|
|
67
|
+
{...inputInnerProps}
|
|
61
68
|
type={isForceVisibleInput ? 'text' : props.type || 'text'}
|
|
62
69
|
ref={ref}
|
|
63
70
|
/>
|
|
@@ -23,7 +23,7 @@ export const InputWrapper = styled.div<{ $width?: string | number }>`
|
|
|
23
23
|
|
|
24
24
|
export const InputContainer = styled.div<{
|
|
25
25
|
disabled?: boolean;
|
|
26
|
-
hasError?: boolean;
|
|
26
|
+
$hasError?: boolean;
|
|
27
27
|
}>`
|
|
28
28
|
display: flex;
|
|
29
29
|
align-items: center;
|
|
@@ -36,8 +36,8 @@ export const InputContainer = styled.div<{
|
|
|
36
36
|
border-color 0.2s,
|
|
37
37
|
background-color 0.2s;
|
|
38
38
|
|
|
39
|
-
${({ hasError, disabled }) => [
|
|
40
|
-
hasError
|
|
39
|
+
${({ $hasError, disabled }) => [
|
|
40
|
+
$hasError
|
|
41
41
|
? css`
|
|
42
42
|
border-color: ${roleRed};
|
|
43
43
|
`
|
package/src/index.ts
CHANGED
|
@@ -44,7 +44,7 @@ export const TableHeader = styled.thead``;
|
|
|
44
44
|
|
|
45
45
|
export const TableBody = styled.tbody``;
|
|
46
46
|
|
|
47
|
-
const Th = styled.th<{ align?: TextAlign }>`
|
|
47
|
+
const Th = styled.th<{ align?: TextAlign; rowSpan?: number }>`
|
|
48
48
|
border: 1px solid ${contents100};
|
|
49
49
|
border-width: 1px 0 1px 0;
|
|
50
50
|
padding: 10px;
|
|
@@ -52,7 +52,7 @@ const Th = styled.th<{ align?: TextAlign }>`
|
|
|
52
52
|
vertical-align: middle;
|
|
53
53
|
`;
|
|
54
54
|
|
|
55
|
-
const Td = styled.td<{ align?: TextAlign }>`
|
|
55
|
+
const Td = styled.td<{ align?: TextAlign; rowSpan?: number }>`
|
|
56
56
|
border-bottom: 1px solid ${contents300};
|
|
57
57
|
vertical-align: top;
|
|
58
58
|
padding: 20px 10px;
|
|
@@ -63,16 +63,22 @@ const Td = styled.td<{ align?: TextAlign }>`
|
|
|
63
63
|
export const TableColumn = ({
|
|
64
64
|
isHeader,
|
|
65
65
|
align = 'left',
|
|
66
|
+
rowSpan,
|
|
66
67
|
children,
|
|
67
68
|
}: {
|
|
68
69
|
isHeader?: boolean;
|
|
69
70
|
align?: TextAlign;
|
|
71
|
+
rowSpan?: number;
|
|
70
72
|
children: ReactNode;
|
|
71
73
|
}) =>
|
|
72
74
|
isHeader ? (
|
|
73
|
-
<Th align={align}
|
|
75
|
+
<Th align={align} rowSpan={rowSpan}>
|
|
76
|
+
{children}
|
|
77
|
+
</Th>
|
|
74
78
|
) : (
|
|
75
|
-
<Td align={align}
|
|
79
|
+
<Td align={align} rowSpan={rowSpan}>
|
|
80
|
+
{children}
|
|
81
|
+
</Td>
|
|
76
82
|
);
|
|
77
83
|
|
|
78
84
|
export const TableRow = styled.tr``;
|