teachable-design-system 0.3.0 → 0.3.3
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/index.cjs.js +875 -64
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +72 -2
- package/dist/index.esm.js +877 -67
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Dropdown/Dropdown.d.ts +1 -1
- package/dist/types/components/Dropdown/Dropdown.d.ts.map +1 -1
- package/dist/types/components/Dropdown/style.d.ts +1 -0
- package/dist/types/components/Dropdown/style.d.ts.map +1 -1
- package/dist/types/components/Table/Table.d.ts +3 -0
- package/dist/types/components/Table/Table.d.ts.map +1 -0
- package/dist/types/components/Table/index.d.ts +3 -0
- package/dist/types/components/Table/index.d.ts.map +1 -0
- package/dist/types/components/Table/style.d.ts +98 -0
- package/dist/types/components/Table/style.d.ts.map +1 -0
- package/dist/types/components/Table/table-body.d.ts +3 -0
- package/dist/types/components/Table/table-body.d.ts.map +1 -0
- package/dist/types/components/Table/table-cell.d.ts +3 -0
- package/dist/types/components/Table/table-cell.d.ts.map +1 -0
- package/dist/types/components/Table/table-header.d.ts +4 -0
- package/dist/types/components/Table/table-header.d.ts.map +1 -0
- package/dist/types/components/index.d.ts +1 -0
- package/dist/types/components/index.d.ts.map +1 -1
- package/dist/types/types/Dropdown.types.d.ts +1 -0
- package/dist/types/types/Dropdown.types.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +6 -0
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/types/table.d.ts +141 -0
- package/dist/types/types/table.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Dropdown/Dropdown.stories.tsx +4 -0
- package/src/components/Dropdown/Dropdown.tsx +2 -1
- package/src/components/Dropdown/style.ts +2 -1
- package/src/components/Sidebar/style.ts +4 -4
- package/src/types/Dropdown.types.ts +1 -0
- package/src/types/index.ts +6 -0
- /package/src/types/{table.d.ts → table.ts} +0 -0
package/dist/index.esm.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import styled from '@emotion/styled';
|
|
3
3
|
import { css } from '@emotion/react';
|
|
4
|
-
import React, { useState } from 'react';
|
|
4
|
+
import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
5
5
|
import element from './assets/icons/checked.png';
|
|
6
6
|
import arrowDownIcon from './assets/icons/arrow-down.png';
|
|
7
|
-
import { Eye, EyeOff } from 'lucide-react';
|
|
7
|
+
import { Eye, EyeOff, ChevronUp, ChevronDown, Copy, ClipboardPaste, Trash2, XCircle } from 'lucide-react';
|
|
8
8
|
import icon from './assets/icons/icon_size.png';
|
|
9
9
|
|
|
10
|
-
const colors = {
|
|
10
|
+
const colors$1 = {
|
|
11
11
|
//mode
|
|
12
12
|
background: {
|
|
13
13
|
'white': '#FFFFFF',
|
|
@@ -742,26 +742,26 @@ const StyledButton = styled.button `
|
|
|
742
742
|
`}
|
|
743
743
|
|
|
744
744
|
${props => props.buttonType === 'primary' && css `
|
|
745
|
-
background-color: ${colors.button["primary-fill"]};
|
|
746
|
-
color: ${colors.text["inverse-static"]};
|
|
745
|
+
background-color: ${colors$1.button["primary-fill"]};
|
|
746
|
+
color: ${colors$1.text["inverse-static"]};
|
|
747
747
|
border: none;
|
|
748
748
|
|
|
749
749
|
&:hover:not(:disabled) {
|
|
750
|
-
background-color: ${colors.button["primary-fill-hover"]};
|
|
750
|
+
background-color: ${colors$1.button["primary-fill-hover"]};
|
|
751
751
|
}
|
|
752
752
|
|
|
753
753
|
&:active:not(:disabled) {
|
|
754
|
-
background-color: ${colors.button["tertiary-fill"]};
|
|
754
|
+
background-color: ${colors$1.button["tertiary-fill"]};
|
|
755
755
|
}
|
|
756
756
|
`}
|
|
757
757
|
|
|
758
758
|
${props => props.buttonType === 'secondary' && css `
|
|
759
|
-
background-color: ${colors.button['secondary-fill']};
|
|
760
|
-
color: ${colors.text['primary']};
|
|
761
|
-
border: 1px solid ${colors.button["secondary-border"]};
|
|
759
|
+
background-color: ${colors$1.button['secondary-fill']};
|
|
760
|
+
color: ${colors$1.text['primary']};
|
|
761
|
+
border: 1px solid ${colors$1.button["secondary-border"]};
|
|
762
762
|
|
|
763
763
|
&:hover:not(:disabled) {
|
|
764
|
-
background-color: ${colors.button['secondary-fill-hover']};
|
|
764
|
+
background-color: ${colors$1.button['secondary-fill-hover']};
|
|
765
765
|
}
|
|
766
766
|
|
|
767
767
|
&:active:not(:disabled) {
|
|
@@ -771,8 +771,8 @@ const StyledButton = styled.button `
|
|
|
771
771
|
|
|
772
772
|
${props => props.buttonType === 'tertiary' && css `
|
|
773
773
|
background-color: transparent;
|
|
774
|
-
color: ${colors.text["basic"]};
|
|
775
|
-
border: 1px solid ${colors.button["tertiary-border"]};
|
|
774
|
+
color: ${colors$1.text["basic"]};
|
|
775
|
+
border: 1px solid ${colors$1.button["tertiary-border"]};
|
|
776
776
|
|
|
777
777
|
&:hover:not(:disabled) {
|
|
778
778
|
background-color: #f0f7ff;
|
|
@@ -810,30 +810,30 @@ const Wrapper$1 = styled.div `
|
|
|
810
810
|
|
|
811
811
|
${state === "disabled" &&
|
|
812
812
|
css `
|
|
813
|
-
background-color: ${colors.surface.disabled};
|
|
814
|
-
border: 1px solid ${colors.border.disabled};
|
|
813
|
+
background-color: ${colors$1.surface.disabled};
|
|
814
|
+
border: 1px solid ${colors$1.border.disabled};
|
|
815
815
|
`}
|
|
816
816
|
|
|
817
817
|
${state === "default" &&
|
|
818
818
|
select === "off" &&
|
|
819
819
|
css `
|
|
820
|
-
background-color: ${colors.surface.white};
|
|
821
|
-
border: 1px solid ${colors.border["gray-dark"]};
|
|
820
|
+
background-color: ${colors$1.surface.white};
|
|
821
|
+
border: 1px solid ${colors$1.border["gray-dark"]};
|
|
822
822
|
|
|
823
823
|
&:hover {
|
|
824
|
-
border-color: ${colors.border.primary};
|
|
824
|
+
border-color: ${colors$1.border.primary};
|
|
825
825
|
}
|
|
826
826
|
`}
|
|
827
827
|
|
|
828
828
|
${state === "default" &&
|
|
829
829
|
(select === "on" || select === "indeterminate") &&
|
|
830
830
|
css `
|
|
831
|
-
background-color: ${colors.element.primary};
|
|
832
|
-
border: 1px solid ${colors.element.primary};
|
|
831
|
+
background-color: ${colors$1.element.primary};
|
|
832
|
+
border: 1px solid ${colors$1.element.primary};
|
|
833
833
|
|
|
834
834
|
&:hover {
|
|
835
|
-
background-color: ${colors.light.primary["60"]};
|
|
836
|
-
border-color: ${colors.light.primary["60"]};
|
|
835
|
+
background-color: ${colors$1.light.primary["60"]};
|
|
836
|
+
border-color: ${colors$1.light.primary["60"]};
|
|
837
837
|
}
|
|
838
838
|
`}
|
|
839
839
|
`;
|
|
@@ -848,8 +848,8 @@ const DashIcon = styled.div `
|
|
|
848
848
|
width: 60%;
|
|
849
849
|
height: 2.5px;
|
|
850
850
|
background-color: ${({ state }) => state === "disabled"
|
|
851
|
-
? colors.element["disabled-dark"]
|
|
852
|
-
: colors.element.inverse};
|
|
851
|
+
? colors$1.element["disabled-dark"]
|
|
852
|
+
: colors$1.element.inverse};
|
|
853
853
|
border-radius: 2px;
|
|
854
854
|
`;
|
|
855
855
|
|
|
@@ -956,15 +956,15 @@ const getOptionSize = (size) => {
|
|
|
956
956
|
};
|
|
957
957
|
const StyledDropDown = styled.button `
|
|
958
958
|
font-family: ${typography.fontFamily.primary};
|
|
959
|
-
background: ${colors.input.surface};
|
|
959
|
+
background: ${colors$1.input.surface};
|
|
960
960
|
border-radius: 8px;
|
|
961
961
|
display: flex;
|
|
962
962
|
flex-direction: column;
|
|
963
963
|
align-items: flex-start;
|
|
964
964
|
gap: 8px;
|
|
965
|
-
width: 320px;
|
|
965
|
+
width: ${({ width }) => width || '320px'};
|
|
966
966
|
|
|
967
|
-
border: ${({ isOpen }) => isOpen ? `2px solid ${colors.input['border-active']}` : `1px solid ${colors.input.border}`};
|
|
967
|
+
border: ${({ isOpen }) => isOpen ? `2px solid ${colors$1.input['border-active']}` : `1px solid ${colors$1.input.border}`};
|
|
968
968
|
${props => getButtonSize(props.size)}
|
|
969
969
|
|
|
970
970
|
`;
|
|
@@ -984,17 +984,17 @@ const StyledText = styled.p `
|
|
|
984
984
|
flex: 1 0 0;
|
|
985
985
|
text-align: left;
|
|
986
986
|
${props => getFontSize(props.size)}
|
|
987
|
-
color: ${({ isOpen }) => isOpen ? colors.text.static : colors.text.disabled};
|
|
987
|
+
color: ${({ isOpen }) => isOpen ? colors$1.text.static : colors$1.text.disabled};
|
|
988
988
|
`;
|
|
989
989
|
const StyledLabel = styled.p `
|
|
990
990
|
font-family: ${typography.fontFamily.primary};
|
|
991
|
-
color: ${colors.text.static};
|
|
991
|
+
color: ${colors$1.text.static};
|
|
992
992
|
`;
|
|
993
993
|
const StyledIcon$1 = styled.div `
|
|
994
994
|
${props => getIconSize(props.size)}
|
|
995
995
|
`;
|
|
996
996
|
const StyledOptions = styled.div `
|
|
997
|
-
border: 1px solid ${colors.border["gray-light"]};
|
|
997
|
+
border: 1px solid ${colors$1.border["gray-light"]};
|
|
998
998
|
border-radius: 8px;
|
|
999
999
|
|
|
1000
1000
|
display: flex;
|
|
@@ -1019,7 +1019,7 @@ const StyledOption = styled.div `
|
|
|
1019
1019
|
|
|
1020
1020
|
border-radius: 8px;
|
|
1021
1021
|
|
|
1022
|
-
color: ${colors.text.subtle};
|
|
1022
|
+
color: ${colors$1.text.subtle};
|
|
1023
1023
|
position: relative;
|
|
1024
1024
|
|
|
1025
1025
|
${props => getFontSize(props.size)}
|
|
@@ -1027,8 +1027,8 @@ const StyledOption = styled.div `
|
|
|
1027
1027
|
|
|
1028
1028
|
|
|
1029
1029
|
${({ isSelected }) => isSelected && css `
|
|
1030
|
-
background: ${colors.action["secondary-selected"]};
|
|
1031
|
-
color: ${colors.text.secondary};
|
|
1030
|
+
background: ${colors$1.action["secondary-selected"]};
|
|
1031
|
+
color: ${colors$1.text.secondary};
|
|
1032
1032
|
|
|
1033
1033
|
padding-left: 36px;
|
|
1034
1034
|
|
|
@@ -1047,14 +1047,14 @@ const StyledOption = styled.div `
|
|
|
1047
1047
|
`}
|
|
1048
1048
|
|
|
1049
1049
|
&:hover {
|
|
1050
|
-
background: ${colors.action["secondary-hover"]};
|
|
1050
|
+
background: ${colors$1.action["secondary-hover"]};
|
|
1051
1051
|
}
|
|
1052
1052
|
&:active {
|
|
1053
|
-
background: ${colors.action["secondary-pressed"]};
|
|
1053
|
+
background: ${colors$1.action["secondary-pressed"]};
|
|
1054
1054
|
}
|
|
1055
1055
|
`;
|
|
1056
1056
|
|
|
1057
|
-
function Dropdown({ size, options, onSelect, label, placeholder }) {
|
|
1057
|
+
function Dropdown({ size, options, onSelect, label, placeholder, width }) {
|
|
1058
1058
|
const [open, setOpen] = React.useState(false);
|
|
1059
1059
|
const [selected, setSelected] = React.useState(null);
|
|
1060
1060
|
const ref = React.useRef(null);
|
|
@@ -1072,7 +1072,7 @@ function Dropdown({ size, options, onSelect, label, placeholder }) {
|
|
|
1072
1072
|
onSelect?.(option);
|
|
1073
1073
|
setOpen(false);
|
|
1074
1074
|
};
|
|
1075
|
-
return (jsxs("div", { style: { position: 'relative' }, children: [jsx(StyledLabel, { children: label }), jsx(StyledDropDown, { onClick: () => setOpen((prev) => !prev), size: size, isOpen: open, children: jsxs(StyledBox, { children: [jsx(StyledText, { size: size, isOpen: open, children: selected ?? placeholder }), jsx(StyledIcon$1, { size: size, children: jsx("img", { src: arrowDownIcon, alt: "dropdown icon", style: { width: '100%', height: '100%' } }) })] }) }), open && (jsx(StyledOptions, { ref: ref, size: size, children: options?.map((option) => (jsx(StyledOption, { onClick: () => handleSelect(option), size: size, isSelected: option === selected, children: option }, option))) }))] }));
|
|
1075
|
+
return (jsxs("div", { style: { position: 'relative' }, children: [jsx(StyledLabel, { children: label }), jsx(StyledDropDown, { onClick: () => setOpen((prev) => !prev), size: size, width: width, isOpen: open, children: jsxs(StyledBox, { children: [jsx(StyledText, { size: size, isOpen: open, children: selected ?? placeholder }), jsx(StyledIcon$1, { size: size, children: jsx("img", { src: arrowDownIcon, alt: "dropdown icon", style: { width: '100%', height: '100%' } }) })] }) }), open && (jsx(StyledOptions, { ref: ref, size: size, children: options?.map((option) => (jsx(StyledOption, { onClick: () => handleSelect(option), size: size, isSelected: option === selected, children: option }, option))) }))] }));
|
|
1076
1076
|
}
|
|
1077
1077
|
|
|
1078
1078
|
const getInputSizeStyle = (size) => {
|
|
@@ -1120,7 +1120,7 @@ const Label = styled.label `
|
|
|
1120
1120
|
line-height: ${typography.label.small.lineHeight};
|
|
1121
1121
|
font-weight: ${typography.label.small.fontWeight};
|
|
1122
1122
|
font-family: ${typography.fontFamily.primary};
|
|
1123
|
-
color: ${colors.text.subtle};
|
|
1123
|
+
color: ${colors$1.text.subtle};
|
|
1124
1124
|
`;
|
|
1125
1125
|
const InputContainer = styled.div `
|
|
1126
1126
|
position: relative;
|
|
@@ -1132,13 +1132,13 @@ const StyledInput = styled.input `
|
|
|
1132
1132
|
width: ${(props) => props.width || "306px"};
|
|
1133
1133
|
padding: ${(props) => (props.isPassword ? "0px 48px 0px 16px" : "0px 16px")};
|
|
1134
1134
|
font-family: ${typography.fontFamily.primary};
|
|
1135
|
-
border: 1px solid ${colors.input.border};
|
|
1135
|
+
border: 1px solid ${colors$1.input.border};
|
|
1136
1136
|
border-radius: 4px;
|
|
1137
1137
|
outline: none;
|
|
1138
1138
|
transition: all 0.2s ease;
|
|
1139
|
-
background-color: ${(props) => props.disabled ? colors.input["surface-disabled"] : colors.input.surface};
|
|
1139
|
+
background-color: ${(props) => props.disabled ? colors$1.input["surface-disabled"] : colors$1.input.surface};
|
|
1140
1140
|
cursor: ${(props) => (props.disabled ? "not-allowed" : "text")};
|
|
1141
|
-
color: ${colors.text.basic};
|
|
1141
|
+
color: ${colors$1.text.basic};
|
|
1142
1142
|
box-sizing: border-box;
|
|
1143
1143
|
|
|
1144
1144
|
${(props) => getInputSizeStyle(props.inputSize)}
|
|
@@ -1149,17 +1149,17 @@ const StyledInput = styled.input `
|
|
|
1149
1149
|
`}
|
|
1150
1150
|
|
|
1151
1151
|
&:focus {
|
|
1152
|
-
border: 1px solid ${colors.input["border-active"]};
|
|
1153
|
-
box-shadow: 0 0 0 3px ${colors.light.primary["5"]};
|
|
1152
|
+
border: 1px solid ${colors$1.input["border-active"]};
|
|
1153
|
+
box-shadow: 0 0 0 3px ${colors$1.light.primary["5"]};
|
|
1154
1154
|
}
|
|
1155
1155
|
|
|
1156
1156
|
&:disabled {
|
|
1157
|
-
border: 1px solid ${colors.input["border-disabled"]};
|
|
1158
|
-
color: ${colors.text.disabled};
|
|
1157
|
+
border: 1px solid ${colors$1.input["border-disabled"]};
|
|
1158
|
+
color: ${colors$1.text.disabled};
|
|
1159
1159
|
}
|
|
1160
1160
|
|
|
1161
1161
|
&::placeholder {
|
|
1162
|
-
color: ${colors.text.disabled};
|
|
1162
|
+
color: ${colors$1.text.disabled};
|
|
1163
1163
|
}
|
|
1164
1164
|
`;
|
|
1165
1165
|
const IconButton = styled.button `
|
|
@@ -1178,7 +1178,7 @@ const IconButton = styled.button `
|
|
|
1178
1178
|
transition: opacity 0.2s ease;
|
|
1179
1179
|
|
|
1180
1180
|
svg {
|
|
1181
|
-
color: ${(props) => props.disabled ? colors.icon.disabled : colors.icon.gray};
|
|
1181
|
+
color: ${(props) => props.disabled ? colors$1.icon.disabled : colors$1.icon.gray};
|
|
1182
1182
|
}
|
|
1183
1183
|
`;
|
|
1184
1184
|
|
|
@@ -1196,14 +1196,14 @@ const StyledIcon = styled.img `
|
|
|
1196
1196
|
`;
|
|
1197
1197
|
const StyledSidebar = styled.div `
|
|
1198
1198
|
display: flex;
|
|
1199
|
-
width:
|
|
1200
|
-
height:
|
|
1199
|
+
width: 208px;
|
|
1200
|
+
height: 621px;
|
|
1201
1201
|
padding: 16px;
|
|
1202
1202
|
flex-direction: column;
|
|
1203
1203
|
align-items: flex-start;
|
|
1204
1204
|
gap: 16px;
|
|
1205
1205
|
|
|
1206
|
-
background: ${colors.background.white};
|
|
1206
|
+
background: ${colors$1.background.white};
|
|
1207
1207
|
`;
|
|
1208
1208
|
const StyledDescription = styled.div `
|
|
1209
1209
|
width: 100%;
|
|
@@ -1212,7 +1212,7 @@ const StyledDescription = styled.div `
|
|
|
1212
1212
|
align-items: flex-start;
|
|
1213
1213
|
gap: 18px;
|
|
1214
1214
|
flex: 1 0 0;
|
|
1215
|
-
max-height: calc(
|
|
1215
|
+
max-height: calc(100% - 40px);
|
|
1216
1216
|
overflow-y: auto;
|
|
1217
1217
|
overflow-x: hidden;
|
|
1218
1218
|
|
|
@@ -1221,21 +1221,21 @@ const StyledDescription = styled.div `
|
|
|
1221
1221
|
}
|
|
1222
1222
|
|
|
1223
1223
|
::-webkit-scrollbar-track {
|
|
1224
|
-
background: ${colors.surface["gray-subtler"]};
|
|
1224
|
+
background: ${colors$1.surface["gray-subtler"]};
|
|
1225
1225
|
border-radius: 4px;
|
|
1226
1226
|
}
|
|
1227
1227
|
|
|
1228
1228
|
::-webkit-scrollbar-thumb {
|
|
1229
|
-
background-color: ${colors.text.subtle};
|
|
1229
|
+
background-color: ${colors$1.text.subtle};
|
|
1230
1230
|
border-radius: 4px;
|
|
1231
1231
|
}
|
|
1232
1232
|
|
|
1233
1233
|
::-webkit-scrollbar-thumb:hover {
|
|
1234
|
-
background-color: ${colors.action["secondary-active"]};
|
|
1234
|
+
background-color: ${colors$1.action["secondary-active"]};
|
|
1235
1235
|
}
|
|
1236
1236
|
|
|
1237
1237
|
::-webkit-scrollbar-thumb:active {
|
|
1238
|
-
background-color: ${colors.action["primary-active"]};
|
|
1238
|
+
background-color: ${colors$1.action["primary-active"]};
|
|
1239
1239
|
}
|
|
1240
1240
|
`;
|
|
1241
1241
|
const StyledButtonArea = styled.div `
|
|
@@ -1266,15 +1266,15 @@ const StyledOpenContents = styled.div `
|
|
|
1266
1266
|
align-items: flex-start;
|
|
1267
1267
|
gap: 8px;
|
|
1268
1268
|
align-self: stretch;
|
|
1269
|
-
width:
|
|
1269
|
+
width: 154px;
|
|
1270
1270
|
|
|
1271
1271
|
border-radius: 12px;
|
|
1272
|
-
background: ${colors.surface["gray-subtler"]};
|
|
1272
|
+
background: ${colors$1.surface["gray-subtler"]};
|
|
1273
1273
|
`;
|
|
1274
1274
|
const StyledOpenContentsText = styled.p `
|
|
1275
1275
|
align-self: stretch;
|
|
1276
1276
|
|
|
1277
|
-
color: ${colors.text.subtle};
|
|
1277
|
+
color: ${colors$1.text.subtle};
|
|
1278
1278
|
|
|
1279
1279
|
font-family: ${typography.fontFamily.primary};
|
|
1280
1280
|
font-size: 13px;
|
|
@@ -1311,7 +1311,7 @@ const Wrapper = styled.div `
|
|
|
1311
1311
|
width: 160px;
|
|
1312
1312
|
height: 100vh;
|
|
1313
1313
|
padding: 16px;
|
|
1314
|
-
background-color: ${colors.surface.white};
|
|
1314
|
+
background-color: ${colors$1.surface.white};
|
|
1315
1315
|
gap: 16px;
|
|
1316
1316
|
`;
|
|
1317
1317
|
const Title = styled.span `
|
|
@@ -1319,7 +1319,7 @@ const Title = styled.span `
|
|
|
1319
1319
|
margin: 0;
|
|
1320
1320
|
${typography.heading.xxsmall}
|
|
1321
1321
|
font-family:${typography.fontFamily.primary};
|
|
1322
|
-
color: ${colors.text.bolder};
|
|
1322
|
+
color: ${colors$1.text.bolder};
|
|
1323
1323
|
`;
|
|
1324
1324
|
const Tablist = styled.div `
|
|
1325
1325
|
display: flex;
|
|
@@ -1330,19 +1330,19 @@ const TabItem = styled.div `
|
|
|
1330
1330
|
padding: 8px 8px;
|
|
1331
1331
|
font-family: ${typography.fontFamily.primary};
|
|
1332
1332
|
${({ isSelected }) => isSelected ? typography.body.xsmallBold : typography.body.xsmall}
|
|
1333
|
-
color: ${({ isSelected }) => isSelected ? colors.text.secondary : colors.text.subtle};
|
|
1334
|
-
background-color: ${({ isSelected }) => isSelected ? colors.action["secondary-selected"] : "transparent"};
|
|
1333
|
+
color: ${({ isSelected }) => isSelected ? colors$1.text.secondary : colors$1.text.subtle};
|
|
1334
|
+
background-color: ${({ isSelected }) => isSelected ? colors$1.action["secondary-selected"] : "transparent"};
|
|
1335
1335
|
border-radius: 4px;
|
|
1336
1336
|
cursor: pointer;
|
|
1337
1337
|
|
|
1338
1338
|
&:hover {
|
|
1339
1339
|
background-color: ${({ isSelected }) => isSelected
|
|
1340
|
-
? colors.action["secondary-selected"]
|
|
1341
|
-
: colors.action["secondary-hover"]};
|
|
1340
|
+
? colors$1.action["secondary-selected"]
|
|
1341
|
+
: colors$1.action["secondary-hover"]};
|
|
1342
1342
|
}
|
|
1343
1343
|
|
|
1344
1344
|
&:active {
|
|
1345
|
-
background-color: ${colors.action["secondary-pressed"]};
|
|
1345
|
+
background-color: ${colors$1.action["secondary-pressed"]};
|
|
1346
1346
|
}
|
|
1347
1347
|
`;
|
|
1348
1348
|
|
|
@@ -1357,5 +1357,815 @@ const TabBar = ({ title, items, defaultSelectedId, onChange }) => {
|
|
|
1357
1357
|
return (jsxs(Wrapper, { children: [title && jsx(Title, { children: title }), jsx(Tablist, { children: items.map((item) => (jsx(TabItem, { isSelected: selectedId === item.id, onClick: () => handleTabClick(item.id), children: item.label }, item.id))) })] }));
|
|
1358
1358
|
};
|
|
1359
1359
|
|
|
1360
|
-
|
|
1360
|
+
// ============================================
|
|
1361
|
+
// 디자인 토큰
|
|
1362
|
+
// ============================================
|
|
1363
|
+
const colors = {
|
|
1364
|
+
header: colors$1.surface['secondary-subtler'],
|
|
1365
|
+
headerHover: colors$1.action['secondary-pressed'],
|
|
1366
|
+
body: colors$1.surface.white,
|
|
1367
|
+
bodyHover: colors$1.surface['gray-subtler'],
|
|
1368
|
+
border: colors$1.border['gray-light'],
|
|
1369
|
+
borderLight: colors$1.border['secondary-light'],
|
|
1370
|
+
text: colors$1.text.bolder,
|
|
1371
|
+
textSecondary: colors$1.text.subtle,
|
|
1372
|
+
scrollThumb: colors$1.surface['gray-subtle'],
|
|
1373
|
+
scrollThumbBorder: colors$1.border.gray,
|
|
1374
|
+
selected: colors$1.surface['information-subtler'],
|
|
1375
|
+
selectedBorder: colors$1.border.primary,
|
|
1376
|
+
disabledText: colors$1.text.disabled,
|
|
1377
|
+
};
|
|
1378
|
+
const spacing = {
|
|
1379
|
+
cellPadding: '4px 16px',
|
|
1380
|
+
headerPadding: '4px 16px',
|
|
1381
|
+
};
|
|
1382
|
+
// ============================================
|
|
1383
|
+
// 레이아웃 컴포넌트
|
|
1384
|
+
// ============================================
|
|
1385
|
+
const TableOuterWrapper = styled.div `
|
|
1386
|
+
position: relative;
|
|
1387
|
+
display: inline-block;
|
|
1388
|
+
outline: none;
|
|
1389
|
+
|
|
1390
|
+
&:focus {
|
|
1391
|
+
outline: none;
|
|
1392
|
+
}
|
|
1393
|
+
`;
|
|
1394
|
+
const TableWrapper = styled.div `
|
|
1395
|
+
display: flex;
|
|
1396
|
+
flex-direction: column;
|
|
1397
|
+
width: 100%;
|
|
1398
|
+
overflow: hidden;
|
|
1399
|
+
`;
|
|
1400
|
+
const TableContainer = styled.div `
|
|
1401
|
+
width: 100%;
|
|
1402
|
+
overflow: auto;
|
|
1403
|
+
position: relative;
|
|
1404
|
+
${({ maxHeight }) => maxHeight && `max-height: ${maxHeight};`}
|
|
1405
|
+
|
|
1406
|
+
/* 스크롤바 스타일 */
|
|
1407
|
+
&::-webkit-scrollbar {
|
|
1408
|
+
width: 20px;
|
|
1409
|
+
height: 20px;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
&::-webkit-scrollbar-track {
|
|
1413
|
+
background: ${colors.body};
|
|
1414
|
+
border: 1px solid ${colors.border};
|
|
1415
|
+
margin-top: 20px; /* 상단 화살표 버튼 높이만큼 여백 */
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
&::-webkit-scrollbar-thumb {
|
|
1419
|
+
background: ${colors.scrollThumb};
|
|
1420
|
+
border: 1px solid ${colors.scrollThumbBorder};
|
|
1421
|
+
|
|
1422
|
+
&:hover {
|
|
1423
|
+
background: ${colors.headerHover};
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
&::-webkit-scrollbar-button:vertical:start:decrement,
|
|
1428
|
+
&::-webkit-scrollbar-button:vertical:end:increment {
|
|
1429
|
+
display: block;
|
|
1430
|
+
height: 20px;
|
|
1431
|
+
background: ${colors.header};
|
|
1432
|
+
border: 1px solid ${colors.borderLight};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
&::-webkit-scrollbar-button:vertical:start:decrement:hover,
|
|
1436
|
+
&::-webkit-scrollbar-button:vertical:end:increment:hover {
|
|
1437
|
+
background: ${colors.headerHover};
|
|
1438
|
+
}
|
|
1439
|
+
`;
|
|
1440
|
+
// ============================================
|
|
1441
|
+
// 테이블 기본 컴포넌트
|
|
1442
|
+
// ============================================
|
|
1443
|
+
const StyledTable = styled.table `
|
|
1444
|
+
width: 100%;
|
|
1445
|
+
border-collapse: separate;
|
|
1446
|
+
border-spacing: 0;
|
|
1447
|
+
table-layout: auto;
|
|
1448
|
+
font-family: ${typography.fontFamily.primary};
|
|
1449
|
+
`;
|
|
1450
|
+
const TableHead = styled.thead `
|
|
1451
|
+
position: sticky;
|
|
1452
|
+
top: 0;
|
|
1453
|
+
z-index: 10;
|
|
1454
|
+
background: ${colors.header};
|
|
1455
|
+
`;
|
|
1456
|
+
const TableBody$1 = styled.tbody ``;
|
|
1457
|
+
const TableRow = styled.tr `
|
|
1458
|
+
&:nth-of-type(even) {
|
|
1459
|
+
${({ striped }) => striped && `background-color: ${colors.bodyHover};`}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
&:hover {
|
|
1463
|
+
background-color: ${colors.bodyHover};
|
|
1464
|
+
}
|
|
1465
|
+
`;
|
|
1466
|
+
// ============================================
|
|
1467
|
+
// 셀 컴포넌트
|
|
1468
|
+
// ============================================
|
|
1469
|
+
const baseCellStyle = `
|
|
1470
|
+
box-sizing: border-box;
|
|
1471
|
+
vertical-align: middle;
|
|
1472
|
+
line-height: 1.5;
|
|
1473
|
+
`;
|
|
1474
|
+
const TableHeaderCell = styled.th `
|
|
1475
|
+
${baseCellStyle}
|
|
1476
|
+
min-width: ${({ width }) => (width ? '0' : '80px')};
|
|
1477
|
+
background: ${colors.header};
|
|
1478
|
+
border: 1px solid ${colors.borderLight};
|
|
1479
|
+
border-left: none;
|
|
1480
|
+
padding: ${spacing.headerPadding};
|
|
1481
|
+
text-align: left;
|
|
1482
|
+
font-weight: 700;
|
|
1483
|
+
font-size: 15px;
|
|
1484
|
+
color: ${colors.text};
|
|
1485
|
+
height: 30px;
|
|
1486
|
+
white-space: nowrap;
|
|
1487
|
+
position: relative;
|
|
1488
|
+
${({ width }) => width && `width: ${width};`}
|
|
1489
|
+
|
|
1490
|
+
&:first-of-type {
|
|
1491
|
+
border-left: 1px solid ${colors.borderLight};
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
${({ sortable }) => sortable &&
|
|
1495
|
+
`
|
|
1496
|
+
cursor: pointer;
|
|
1497
|
+
user-select: none;
|
|
1498
|
+
|
|
1499
|
+
&:hover {
|
|
1500
|
+
background: ${colors.headerHover};
|
|
1501
|
+
}
|
|
1502
|
+
`}
|
|
1503
|
+
`;
|
|
1504
|
+
const TableDataCell = styled.td `
|
|
1505
|
+
${baseCellStyle}
|
|
1506
|
+
min-width: ${({ width }) => (width ? '0' : '80px')};
|
|
1507
|
+
background: ${({ isHeaderColumn, isSelected, $rowSelected }) => isSelected ? colors.selected : (isHeaderColumn ? colors.header : ($rowSelected ? 'inherit' : colors.body))};
|
|
1508
|
+
border-right: 1px solid ${({ isHeaderColumn }) => isHeaderColumn ? colors.borderLight : colors.border};
|
|
1509
|
+
border-bottom: 1px solid ${({ isHeaderColumn }) => isHeaderColumn ? colors.borderLight : colors.border};
|
|
1510
|
+
border-left: none;
|
|
1511
|
+
border-top: none;
|
|
1512
|
+
padding: ${({ isHeaderColumn }) => (isHeaderColumn ? spacing.headerPadding : spacing.cellPadding)};
|
|
1513
|
+
font-weight: ${({ isHeaderColumn }) => (isHeaderColumn ? 700 : 400)};
|
|
1514
|
+
font-size: ${({ isHeaderColumn }) => (isHeaderColumn ? '15px' : '13px')};
|
|
1515
|
+
color: ${({ isHeaderColumn }) => (isHeaderColumn ? colors.text : colors.textSecondary)};
|
|
1516
|
+
height: ${({ height }) => height ?? '30px'};
|
|
1517
|
+
position: relative;
|
|
1518
|
+
user-select: none;
|
|
1519
|
+
|
|
1520
|
+
&:first-of-type {
|
|
1521
|
+
border-left: 1px solid ${({ isHeaderColumn }) => isHeaderColumn ? colors.borderLight : colors.border};
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
${({ isSelected, $edgeTop, $edgeBottom, $edgeLeft, $edgeRight }) => isSelected &&
|
|
1525
|
+
`
|
|
1526
|
+
z-index: 1;
|
|
1527
|
+
box-shadow:
|
|
1528
|
+
${$edgeTop ? `inset 0 2px 0 0 ${colors.selectedBorder}` : `inset 0 0.5px 0 0 ${colors.selectedBorder}50`}${$edgeBottom ? `, inset 0 -2px 0 0 ${colors.selectedBorder}` : `, inset 0 -0.5px 0 0 ${colors.selectedBorder}50`}${$edgeLeft ? `, inset 2px 0 0 0 ${colors.selectedBorder}` : `, inset 0.5px 0 0 0 ${colors.selectedBorder}50`}${$edgeRight ? `, inset -2px 0 0 0 ${colors.selectedBorder}` : `, inset -0.5px 0 0 0 ${colors.selectedBorder}50`};
|
|
1529
|
+
`}
|
|
1530
|
+
|
|
1531
|
+
${({ editable, isSelected, $rowSelected }) => editable && !isSelected && !$rowSelected &&
|
|
1532
|
+
`
|
|
1533
|
+
cursor: cell;
|
|
1534
|
+
&:hover {
|
|
1535
|
+
background-color: ${colors.bodyHover};
|
|
1536
|
+
}
|
|
1537
|
+
`}
|
|
1538
|
+
|
|
1539
|
+
${({ isSelected }) => isSelected &&
|
|
1540
|
+
`
|
|
1541
|
+
&:hover {
|
|
1542
|
+
background-color: ${colors.selected};
|
|
1543
|
+
}
|
|
1544
|
+
`}
|
|
1545
|
+
`;
|
|
1546
|
+
// ============================================
|
|
1547
|
+
// 정렬 및 입력 컴포넌트
|
|
1548
|
+
// ============================================
|
|
1549
|
+
const SortIcon = styled.span `
|
|
1550
|
+
display: inline-flex;
|
|
1551
|
+
flex-direction: column;
|
|
1552
|
+
margin-left: 4px;
|
|
1553
|
+
opacity: ${({ active }) => (active ? 1 : 0.3)};
|
|
1554
|
+
|
|
1555
|
+
svg {
|
|
1556
|
+
width: 12px;
|
|
1557
|
+
height: 12px;
|
|
1558
|
+
}
|
|
1559
|
+
`;
|
|
1560
|
+
const EditableInput = styled.input `
|
|
1561
|
+
width: 100%;
|
|
1562
|
+
border: none;
|
|
1563
|
+
outline: none;
|
|
1564
|
+
background: transparent;
|
|
1565
|
+
font: inherit;
|
|
1566
|
+
color: inherit;
|
|
1567
|
+
margin: -12px -16px;
|
|
1568
|
+
padding: ${spacing.cellPadding};
|
|
1569
|
+
|
|
1570
|
+
&:focus {
|
|
1571
|
+
outline: none;
|
|
1572
|
+
}
|
|
1573
|
+
`;
|
|
1574
|
+
// ============================================
|
|
1575
|
+
// 스크롤 컨트롤
|
|
1576
|
+
// ============================================
|
|
1577
|
+
const ScrollContainer = styled.div `
|
|
1578
|
+
display: flex;
|
|
1579
|
+
flex-direction: column;
|
|
1580
|
+
position: absolute;
|
|
1581
|
+
right: 0;
|
|
1582
|
+
top: 0;
|
|
1583
|
+
`;
|
|
1584
|
+
const ScrollButton = styled.button `
|
|
1585
|
+
width: 20px;
|
|
1586
|
+
height: 20px;
|
|
1587
|
+
padding: 0;
|
|
1588
|
+
background: ${colors.header};
|
|
1589
|
+
border: 1px solid ${colors.borderLight};
|
|
1590
|
+
cursor: pointer;
|
|
1591
|
+
display: flex;
|
|
1592
|
+
align-items: center;
|
|
1593
|
+
justify-content: center;
|
|
1594
|
+
transition: background-color 0.2s;
|
|
1595
|
+
|
|
1596
|
+
&:hover {
|
|
1597
|
+
background: ${colors.headerHover};
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
&:disabled {
|
|
1601
|
+
opacity: 0.5;
|
|
1602
|
+
cursor: not-allowed;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
svg {
|
|
1606
|
+
width: 14px;
|
|
1607
|
+
height: 14px;
|
|
1608
|
+
color: ${colors.text};
|
|
1609
|
+
}
|
|
1610
|
+
`;
|
|
1611
|
+
// ============================================
|
|
1612
|
+
// 컨텍스트 메뉴
|
|
1613
|
+
// ============================================
|
|
1614
|
+
const ContextMenuOverlay = styled.div `
|
|
1615
|
+
position: fixed;
|
|
1616
|
+
top: 0;
|
|
1617
|
+
left: 0;
|
|
1618
|
+
right: 0;
|
|
1619
|
+
bottom: 0;
|
|
1620
|
+
z-index: 999;
|
|
1621
|
+
`;
|
|
1622
|
+
const ContextMenu = styled.div `
|
|
1623
|
+
position: fixed;
|
|
1624
|
+
top: ${({ y }) => y}px;
|
|
1625
|
+
left: ${({ x }) => x}px;
|
|
1626
|
+
z-index: 1000;
|
|
1627
|
+
min-width: 160px;
|
|
1628
|
+
background: ${colors.body};
|
|
1629
|
+
border: 1px solid ${colors.border};
|
|
1630
|
+
border-radius: 6px;
|
|
1631
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
1632
|
+
padding: 4px 0;
|
|
1633
|
+
font-family: ${typography.fontFamily.primary};
|
|
1634
|
+
`;
|
|
1635
|
+
const ContextMenuItem = styled.button `
|
|
1636
|
+
width: 100%;
|
|
1637
|
+
padding: 8px 12px;
|
|
1638
|
+
border: none;
|
|
1639
|
+
background: transparent;
|
|
1640
|
+
text-align: left;
|
|
1641
|
+
font-size: 13px;
|
|
1642
|
+
color: ${({ disabled }) => (disabled ? colors.disabledText : colors.text)};
|
|
1643
|
+
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
|
1644
|
+
display: flex;
|
|
1645
|
+
align-items: center;
|
|
1646
|
+
gap: 8px;
|
|
1647
|
+
|
|
1648
|
+
&:hover {
|
|
1649
|
+
background: ${({ disabled }) => (disabled ? 'transparent' : colors.bodyHover)};
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
span.shortcut {
|
|
1653
|
+
margin-left: auto;
|
|
1654
|
+
font-size: 11px;
|
|
1655
|
+
color: ${colors.disabledText};
|
|
1656
|
+
}
|
|
1657
|
+
`;
|
|
1658
|
+
const ContextMenuDivider = styled.div `
|
|
1659
|
+
height: 1px;
|
|
1660
|
+
background: ${colors.borderLight};
|
|
1661
|
+
margin: 4px 0;
|
|
1662
|
+
`;
|
|
1663
|
+
|
|
1664
|
+
/** 정렬 아이콘 컴포넌트 */
|
|
1665
|
+
function SortIndicator({ isActive, direction }) {
|
|
1666
|
+
if (!isActive || !direction) {
|
|
1667
|
+
return jsx(ChevronUp, { style: { opacity: 0.3 } });
|
|
1668
|
+
}
|
|
1669
|
+
return direction === 'asc' ? jsx(ChevronUp, {}) : jsx(ChevronDown, {});
|
|
1670
|
+
}
|
|
1671
|
+
/** 테이블 헤더 */
|
|
1672
|
+
function TableHeader({ columns, sortColumn, sortDirection, onSort, }) {
|
|
1673
|
+
const handleClick = (key, sortable) => {
|
|
1674
|
+
if (sortable)
|
|
1675
|
+
onSort?.(key);
|
|
1676
|
+
};
|
|
1677
|
+
return (jsx(TableHead, { children: jsx(TableRow, { children: columns.map(({ key, header, width, sortable, rowSpan, colSpan }) => {
|
|
1678
|
+
const isActive = sortColumn === key;
|
|
1679
|
+
return (jsxs(TableHeaderCell, { width: width, sortable: sortable, rowSpan: rowSpan, colSpan: colSpan, onClick: () => handleClick(key, sortable), children: [header, sortable && (jsx(SortIcon, { active: isActive, direction: isActive ? sortDirection : null, children: jsx(SortIndicator, { isActive: isActive, direction: sortDirection }) }))] }, key));
|
|
1680
|
+
}) }) }));
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
const formatValue = (val, dataType) => {
|
|
1684
|
+
if (val == null)
|
|
1685
|
+
return '';
|
|
1686
|
+
switch (dataType) {
|
|
1687
|
+
case 'number':
|
|
1688
|
+
return typeof val === 'number' ? val.toLocaleString() : String(val);
|
|
1689
|
+
case 'date':
|
|
1690
|
+
return val instanceof Date ? val.toLocaleDateString('ko-KR') : String(val);
|
|
1691
|
+
case 'boolean':
|
|
1692
|
+
return val ? '예' : '아니오';
|
|
1693
|
+
default:
|
|
1694
|
+
return String(val);
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
const parseValue = (val, dataType) => {
|
|
1698
|
+
switch (dataType) {
|
|
1699
|
+
case 'number': {
|
|
1700
|
+
const num = parseFloat(val.replace(/,/g, ''));
|
|
1701
|
+
return isNaN(num) ? 0 : num;
|
|
1702
|
+
}
|
|
1703
|
+
case 'date':
|
|
1704
|
+
return new Date(val);
|
|
1705
|
+
case 'boolean':
|
|
1706
|
+
return val === '예' || val === 'true' || val === '1';
|
|
1707
|
+
default:
|
|
1708
|
+
return val;
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
const getInputType = (dataType) => {
|
|
1712
|
+
if (dataType === 'number')
|
|
1713
|
+
return 'number';
|
|
1714
|
+
if (dataType === 'date')
|
|
1715
|
+
return 'date';
|
|
1716
|
+
return 'text';
|
|
1717
|
+
};
|
|
1718
|
+
function TableCell({ value, editable = false, width, height, rowHeight, dataType = 'text', isHeaderColumn = false, isSelected = false, rowSelected = false, isEditingRequested, startEditingToken, startEditingValue, selectionEdge, rowSpan, colSpan, onEdit, render, onMouseDown, onMouseEnter, onMouseUp, }) {
|
|
1719
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
1720
|
+
const [editValue, setEditValue] = useState('');
|
|
1721
|
+
const lastStartTokenRef = useRef(undefined);
|
|
1722
|
+
const startEditing = useCallback(() => {
|
|
1723
|
+
if (!editable)
|
|
1724
|
+
return;
|
|
1725
|
+
setEditValue(formatValue(value, dataType));
|
|
1726
|
+
setIsEditing(true);
|
|
1727
|
+
}, [editable, value, dataType]);
|
|
1728
|
+
// 부모에서 "편집 시작" 요청이 오면 (예: 선택 셀에서 타이핑) 편집 모드로 진입
|
|
1729
|
+
useEffect(() => {
|
|
1730
|
+
if (!isEditingRequested)
|
|
1731
|
+
return;
|
|
1732
|
+
if (startEditingToken == null)
|
|
1733
|
+
return;
|
|
1734
|
+
if (lastStartTokenRef.current === startEditingToken)
|
|
1735
|
+
return;
|
|
1736
|
+
lastStartTokenRef.current = startEditingToken;
|
|
1737
|
+
if (!editable)
|
|
1738
|
+
return;
|
|
1739
|
+
const nextValue = startEditingValue ?? formatValue(value, dataType);
|
|
1740
|
+
setEditValue(nextValue);
|
|
1741
|
+
setIsEditing(true);
|
|
1742
|
+
}, [isEditingRequested, startEditingToken, startEditingValue, editable, value, dataType]);
|
|
1743
|
+
const save = useCallback(() => {
|
|
1744
|
+
onEdit?.(parseValue(editValue, dataType));
|
|
1745
|
+
setIsEditing(false);
|
|
1746
|
+
}, [editValue, dataType, onEdit]);
|
|
1747
|
+
const cancel = useCallback(() => {
|
|
1748
|
+
setEditValue('');
|
|
1749
|
+
setIsEditing(false);
|
|
1750
|
+
}, []);
|
|
1751
|
+
const handleKeyDown = useCallback((e) => {
|
|
1752
|
+
if (e.key === 'Enter') {
|
|
1753
|
+
e.preventDefault();
|
|
1754
|
+
save();
|
|
1755
|
+
}
|
|
1756
|
+
else if (e.key === 'Escape') {
|
|
1757
|
+
e.preventDefault();
|
|
1758
|
+
cancel();
|
|
1759
|
+
}
|
|
1760
|
+
}, [save, cancel]);
|
|
1761
|
+
const handleMouseDown = useCallback((e) => {
|
|
1762
|
+
if (isEditing)
|
|
1763
|
+
return;
|
|
1764
|
+
e.preventDefault();
|
|
1765
|
+
onMouseDown?.();
|
|
1766
|
+
}, [isEditing, onMouseDown]);
|
|
1767
|
+
const handleMouseEnter = useCallback(() => {
|
|
1768
|
+
if (isEditing)
|
|
1769
|
+
return;
|
|
1770
|
+
onMouseEnter?.();
|
|
1771
|
+
}, [isEditing, onMouseEnter]);
|
|
1772
|
+
const displayValue = render ? render(value) : formatValue(value, dataType);
|
|
1773
|
+
return (jsx(TableDataCell, { editable: editable, width: width, height: height || rowHeight, isHeaderColumn: isHeaderColumn, isSelected: isSelected, "$rowSelected": rowSelected, "$edgeTop": selectionEdge?.top, "$edgeBottom": selectionEdge?.bottom, "$edgeLeft": selectionEdge?.left, "$edgeRight": selectionEdge?.right, rowSpan: rowSpan, colSpan: colSpan, onDoubleClick: startEditing, onMouseDown: handleMouseDown, onMouseEnter: handleMouseEnter, onMouseUp: onMouseUp, children: isEditing ? (jsx(EditableInput, { type: getInputType(dataType), value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: save, onKeyDown: handleKeyDown, autoFocus: true })) : (displayValue) }));
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
const getSelectionInfo = (rowIndex, colIndex, start, end) => {
|
|
1777
|
+
if (!start || !end) {
|
|
1778
|
+
return { isSelected: false, edge: { top: false, bottom: false, left: false, right: false } };
|
|
1779
|
+
}
|
|
1780
|
+
const minRow = Math.min(start.row, end.row);
|
|
1781
|
+
const maxRow = Math.max(start.row, end.row);
|
|
1782
|
+
const minCol = Math.min(start.col, end.col);
|
|
1783
|
+
const maxCol = Math.max(start.col, end.col);
|
|
1784
|
+
const isSelected = rowIndex >= minRow && rowIndex <= maxRow && colIndex >= minCol && colIndex <= maxCol;
|
|
1785
|
+
if (!isSelected) {
|
|
1786
|
+
return { isSelected: false, edge: { top: false, bottom: false, left: false, right: false } };
|
|
1787
|
+
}
|
|
1788
|
+
return {
|
|
1789
|
+
isSelected: true,
|
|
1790
|
+
edge: {
|
|
1791
|
+
top: rowIndex === minRow,
|
|
1792
|
+
bottom: rowIndex === maxRow,
|
|
1793
|
+
left: colIndex === minCol,
|
|
1794
|
+
right: colIndex === maxCol,
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
};
|
|
1798
|
+
function TableBody({ columns, data, rowHeight, onCellEdit, selectionStart, selectionEnd, editingCell, editStartValue, editToken, onCellMouseDown, onCellMouseEnter, onCellMouseUp, enableRowSelection, selectedRowIndex, hoveredRowIndex, onRowClick, onRowHover, }) {
|
|
1799
|
+
const getRowBackground = (rowIndex) => {
|
|
1800
|
+
if (!enableRowSelection)
|
|
1801
|
+
return undefined;
|
|
1802
|
+
if (selectedRowIndex === rowIndex)
|
|
1803
|
+
return '#e7f4fe';
|
|
1804
|
+
if (hoveredRowIndex === rowIndex)
|
|
1805
|
+
return '#f4f5f6';
|
|
1806
|
+
return undefined;
|
|
1807
|
+
};
|
|
1808
|
+
return (jsx(TableBody$1, { children: data.map((row, rowIndex) => (jsx(TableRow, { onClick: enableRowSelection ? () => onRowClick?.(rowIndex) : undefined, onMouseEnter: enableRowSelection ? () => onRowHover?.(rowIndex) : undefined, onMouseLeave: enableRowSelection ? () => onRowHover?.(null) : undefined, style: {
|
|
1809
|
+
cursor: enableRowSelection ? 'pointer' : undefined,
|
|
1810
|
+
backgroundColor: getRowBackground(rowIndex),
|
|
1811
|
+
transition: enableRowSelection ? 'background-color 0.15s ease' : undefined,
|
|
1812
|
+
}, children: columns.map((col, colIndex) => {
|
|
1813
|
+
const { isSelected, edge } = getSelectionInfo(rowIndex, colIndex, selectionStart, selectionEnd);
|
|
1814
|
+
const isEditingRequested = !!editingCell && editingCell.row === rowIndex && editingCell.col === colIndex;
|
|
1815
|
+
return (jsx(TableCell, { value: row[col.key], editable: col.editable !== false, width: col.width, height: col.height, rowHeight: rowHeight, dataType: col.dataType, isHeaderColumn: col.isHeaderColumn, isSelected: isSelected, rowSelected: enableRowSelection && (selectedRowIndex === rowIndex || hoveredRowIndex === rowIndex), isEditingRequested: isEditingRequested, startEditingToken: editToken, startEditingValue: isEditingRequested ? editStartValue : null, selectionEdge: isSelected ? edge : undefined, rowSpan: col.rowSpan, colSpan: col.colSpan, onEdit: (value) => onCellEdit?.(rowIndex, col.key, value), render: col.render ? (value) => col.render(value, row, rowIndex) : undefined, onMouseDown: enableRowSelection ? undefined : () => onCellMouseDown?.(rowIndex, colIndex), onMouseEnter: enableRowSelection ? undefined : () => onCellMouseEnter?.(rowIndex, colIndex), onMouseUp: enableRowSelection ? undefined : onCellMouseUp }, `${rowIndex}-${col.key}`));
|
|
1816
|
+
}) }, rowIndex))) }));
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
const compareValues = (a, b, dataType) => {
|
|
1820
|
+
if (a == null)
|
|
1821
|
+
return 1;
|
|
1822
|
+
if (b == null)
|
|
1823
|
+
return -1;
|
|
1824
|
+
switch (dataType) {
|
|
1825
|
+
case 'number':
|
|
1826
|
+
return Number(a) - Number(b);
|
|
1827
|
+
case 'date':
|
|
1828
|
+
return new Date(a).getTime() - new Date(b).getTime();
|
|
1829
|
+
case 'boolean':
|
|
1830
|
+
return (a ? 1 : 0) - (b ? 1 : 0);
|
|
1831
|
+
default:
|
|
1832
|
+
return String(a).localeCompare(String(b), 'ko-KR');
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
const getNextSortDirection = (current) => {
|
|
1836
|
+
if (current === 'asc')
|
|
1837
|
+
return 'desc';
|
|
1838
|
+
if (current === 'desc')
|
|
1839
|
+
return null;
|
|
1840
|
+
return 'asc';
|
|
1841
|
+
};
|
|
1842
|
+
const formatCellValue = (value) => {
|
|
1843
|
+
if (value == null)
|
|
1844
|
+
return '';
|
|
1845
|
+
if (value instanceof Date)
|
|
1846
|
+
return value.toLocaleDateString('ko-KR');
|
|
1847
|
+
if (typeof value === 'boolean')
|
|
1848
|
+
return value ? '예' : '아니오';
|
|
1849
|
+
if (typeof value === 'number')
|
|
1850
|
+
return value.toString();
|
|
1851
|
+
return String(value);
|
|
1852
|
+
};
|
|
1853
|
+
function Table({ columns, data, onCellEdit, onSort, onSelectionChange, onPaste, maxHeight, rowHeight, className, enableRowSelection, selectedRowIndex, onRowClick, enableKeyboardNavigation, }) {
|
|
1854
|
+
const outerRef = useRef(null);
|
|
1855
|
+
const containerRef = useRef(null);
|
|
1856
|
+
const [sortColumn, setSortColumn] = useState(null);
|
|
1857
|
+
const [sortDirection, setSortDirection] = useState(null);
|
|
1858
|
+
const [isSelecting, setIsSelecting] = useState(false);
|
|
1859
|
+
const [selectionStart, setSelectionStart] = useState(null);
|
|
1860
|
+
const [selectionEnd, setSelectionEnd] = useState(null);
|
|
1861
|
+
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 });
|
|
1862
|
+
const [hoveredRowIndex, setHoveredRowIndex] = useState(null);
|
|
1863
|
+
const [editingCell, setEditingCell] = useState(null);
|
|
1864
|
+
const [editStartValue, setEditStartValue] = useState(null);
|
|
1865
|
+
const [editToken, setEditToken] = useState(0);
|
|
1866
|
+
const sortedData = useMemo(() => {
|
|
1867
|
+
if (!sortColumn || !sortDirection)
|
|
1868
|
+
return data;
|
|
1869
|
+
const column = columns.find((col) => col.key === sortColumn);
|
|
1870
|
+
if (!column)
|
|
1871
|
+
return data;
|
|
1872
|
+
return [...data].sort((a, b) => {
|
|
1873
|
+
const aVal = a[sortColumn];
|
|
1874
|
+
const bVal = b[sortColumn];
|
|
1875
|
+
const result = column.sortFn
|
|
1876
|
+
? column.sortFn(aVal, bVal)
|
|
1877
|
+
: compareValues(aVal, bVal, column.dataType);
|
|
1878
|
+
return sortDirection === 'asc' ? result : -result;
|
|
1879
|
+
});
|
|
1880
|
+
}, [data, sortColumn, sortDirection, columns]);
|
|
1881
|
+
const handleSort = useCallback((columnKey) => {
|
|
1882
|
+
const isSameColumn = sortColumn === columnKey;
|
|
1883
|
+
const nextDirection = isSameColumn
|
|
1884
|
+
? getNextSortDirection(sortDirection)
|
|
1885
|
+
: 'asc';
|
|
1886
|
+
setSortColumn(nextDirection ? columnKey : null);
|
|
1887
|
+
setSortDirection(nextDirection);
|
|
1888
|
+
onSort?.(columnKey, nextDirection);
|
|
1889
|
+
}, [sortColumn, sortDirection, onSort]);
|
|
1890
|
+
const scroll = useCallback((delta) => {
|
|
1891
|
+
containerRef.current?.scrollBy({ top: delta, behavior: 'smooth' });
|
|
1892
|
+
}, []);
|
|
1893
|
+
const handleCellMouseDown = useCallback((rowIndex, colIndex) => {
|
|
1894
|
+
setIsSelecting(true);
|
|
1895
|
+
setSelectionStart({ row: rowIndex, col: colIndex });
|
|
1896
|
+
setSelectionEnd({ row: rowIndex, col: colIndex });
|
|
1897
|
+
}, []);
|
|
1898
|
+
const handleCellMouseEnter = useCallback((rowIndex, colIndex) => {
|
|
1899
|
+
if (isSelecting) {
|
|
1900
|
+
setSelectionEnd({ row: rowIndex, col: colIndex });
|
|
1901
|
+
}
|
|
1902
|
+
}, [isSelecting]);
|
|
1903
|
+
const handleCellMouseUp = useCallback(() => {
|
|
1904
|
+
if (isSelecting && selectionStart && selectionEnd) {
|
|
1905
|
+
const cells = [];
|
|
1906
|
+
const minRow = Math.min(selectionStart.row, selectionEnd.row);
|
|
1907
|
+
const maxRow = Math.max(selectionStart.row, selectionEnd.row);
|
|
1908
|
+
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
|
1909
|
+
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
|
1910
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
1911
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
1912
|
+
cells.push({ row: r, col: c });
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
onSelectionChange?.(cells);
|
|
1916
|
+
}
|
|
1917
|
+
setIsSelecting(false);
|
|
1918
|
+
}, [isSelecting, selectionStart, selectionEnd, onSelectionChange]);
|
|
1919
|
+
const getSelectedData = useCallback(() => {
|
|
1920
|
+
if (!selectionStart || !selectionEnd)
|
|
1921
|
+
return '';
|
|
1922
|
+
const minRow = Math.min(selectionStart.row, selectionEnd.row);
|
|
1923
|
+
const maxRow = Math.max(selectionStart.row, selectionEnd.row);
|
|
1924
|
+
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
|
1925
|
+
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
|
1926
|
+
const rows = [];
|
|
1927
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
1928
|
+
const cols = [];
|
|
1929
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
1930
|
+
const colKey = columns[c]?.key;
|
|
1931
|
+
const value = sortedData[r]?.[colKey];
|
|
1932
|
+
cols.push(formatCellValue(value));
|
|
1933
|
+
}
|
|
1934
|
+
rows.push(cols.join('\t'));
|
|
1935
|
+
}
|
|
1936
|
+
return rows.join('\n');
|
|
1937
|
+
}, [selectionStart, selectionEnd, columns, sortedData]);
|
|
1938
|
+
const handleKeyDown = useCallback((e) => {
|
|
1939
|
+
// 셀 편집(input/textarea) 중에는 전역 키 핸들러가 Backspace/Delete 등을 가로채지 않도록 무시
|
|
1940
|
+
const target = e.target;
|
|
1941
|
+
if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) {
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
// 행 선택 모드에서 키보드 네비게이션
|
|
1945
|
+
if (enableRowSelection && enableKeyboardNavigation && onRowClick) {
|
|
1946
|
+
if (e.key === 'ArrowUp') {
|
|
1947
|
+
e.preventDefault();
|
|
1948
|
+
const currentIndex = selectedRowIndex ?? 0;
|
|
1949
|
+
const newIndex = Math.max(0, currentIndex - 1);
|
|
1950
|
+
onRowClick(newIndex, sortedData[newIndex]);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
if (e.key === 'ArrowDown') {
|
|
1954
|
+
e.preventDefault();
|
|
1955
|
+
const currentIndex = selectedRowIndex ?? -1;
|
|
1956
|
+
const newIndex = Math.min(sortedData.length - 1, currentIndex + 1);
|
|
1957
|
+
onRowClick(newIndex, sortedData[newIndex]);
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
if (!selectionStart || !selectionEnd)
|
|
1962
|
+
return;
|
|
1963
|
+
// 셀 선택 상태에서 문자 입력 시 즉시 편집 모드로 전환 (스프레드시트 UX)
|
|
1964
|
+
// - 단일 셀 선택일 때만 동작
|
|
1965
|
+
// - onCellEdit가 있어야 의미 있는 편집이 가능
|
|
1966
|
+
if (onCellEdit &&
|
|
1967
|
+
!e.ctrlKey &&
|
|
1968
|
+
!e.metaKey &&
|
|
1969
|
+
!e.altKey &&
|
|
1970
|
+
e.key.length === 1) {
|
|
1971
|
+
const isSingleCell = selectionStart.row === selectionEnd.row && selectionStart.col === selectionEnd.col;
|
|
1972
|
+
if (!isSingleCell) {
|
|
1973
|
+
setSelectionEnd(selectionStart);
|
|
1974
|
+
}
|
|
1975
|
+
e.preventDefault();
|
|
1976
|
+
setEditingCell({ row: selectionStart.row, col: selectionStart.col });
|
|
1977
|
+
setEditStartValue(e.key);
|
|
1978
|
+
setEditToken((t) => t + 1);
|
|
1979
|
+
setContextMenu({ visible: false, x: 0, y: 0 });
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
|
|
1983
|
+
e.preventDefault();
|
|
1984
|
+
const text = getSelectedData();
|
|
1985
|
+
navigator.clipboard.writeText(text).catch(console.error);
|
|
1986
|
+
}
|
|
1987
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'v') {
|
|
1988
|
+
e.preventDefault();
|
|
1989
|
+
navigator.clipboard.readText().then((text) => {
|
|
1990
|
+
if (!text || !selectionStart)
|
|
1991
|
+
return;
|
|
1992
|
+
const rows = text
|
|
1993
|
+
.replace(/\r\n/g, '\n')
|
|
1994
|
+
.replace(/\r/g, '\n')
|
|
1995
|
+
.split('\n')
|
|
1996
|
+
.filter((row, idx, arr) => !(idx === arr.length - 1 && row === ''))
|
|
1997
|
+
.map(row => row.split('\t'));
|
|
1998
|
+
const startRow = Math.min(selectionStart.row, selectionEnd?.row ?? selectionStart.row);
|
|
1999
|
+
const startCol = Math.min(selectionStart.col, selectionEnd?.col ?? selectionStart.col);
|
|
2000
|
+
if (onPaste) {
|
|
2001
|
+
onPaste(startRow, startCol, rows);
|
|
2002
|
+
}
|
|
2003
|
+
else if (onCellEdit) {
|
|
2004
|
+
rows.forEach((row, rIdx) => {
|
|
2005
|
+
row.forEach((cellValue, cIdx) => {
|
|
2006
|
+
const targetRow = startRow + rIdx;
|
|
2007
|
+
const targetCol = startCol + cIdx;
|
|
2008
|
+
if (targetRow < data.length && targetCol < columns.length) {
|
|
2009
|
+
const col = columns[targetCol];
|
|
2010
|
+
if (col.editable !== false) {
|
|
2011
|
+
let parsedValue = cellValue;
|
|
2012
|
+
if (col.dataType === 'number') {
|
|
2013
|
+
const num = parseFloat(cellValue.replace(/,/g, ''));
|
|
2014
|
+
parsedValue = isNaN(num) ? 0 : num;
|
|
2015
|
+
}
|
|
2016
|
+
onCellEdit(targetRow, col.key, parsedValue);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
}).catch(console.error);
|
|
2023
|
+
}
|
|
2024
|
+
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
2025
|
+
if (!onCellEdit)
|
|
2026
|
+
return;
|
|
2027
|
+
e.preventDefault();
|
|
2028
|
+
const minRow = Math.min(selectionStart.row, selectionEnd.row);
|
|
2029
|
+
const maxRow = Math.max(selectionStart.row, selectionEnd.row);
|
|
2030
|
+
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
|
2031
|
+
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
|
2032
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
2033
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
2034
|
+
const col = columns[c];
|
|
2035
|
+
if (col.editable !== false) {
|
|
2036
|
+
onCellEdit(r, col.key, '');
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
if (e.key === 'Escape') {
|
|
2042
|
+
setSelectionStart(null);
|
|
2043
|
+
setSelectionEnd(null);
|
|
2044
|
+
setContextMenu({ visible: false, x: 0, y: 0 });
|
|
2045
|
+
}
|
|
2046
|
+
}, [enableRowSelection, enableKeyboardNavigation, selectedRowIndex, sortedData, onRowClick, selectionStart, selectionEnd, getSelectedData, onPaste, onCellEdit, data, columns]);
|
|
2047
|
+
// 표 밖을 클릭하면 셀 선택(타겟) 해제
|
|
2048
|
+
useEffect(() => {
|
|
2049
|
+
const handleMouseDownOutside = (event) => {
|
|
2050
|
+
const root = outerRef.current;
|
|
2051
|
+
const target = event.target;
|
|
2052
|
+
if (!root || !target)
|
|
2053
|
+
return;
|
|
2054
|
+
if (root.contains(target))
|
|
2055
|
+
return;
|
|
2056
|
+
setIsSelecting(false);
|
|
2057
|
+
setSelectionStart(null);
|
|
2058
|
+
setSelectionEnd(null);
|
|
2059
|
+
setEditingCell(null);
|
|
2060
|
+
setEditStartValue(null);
|
|
2061
|
+
setContextMenu({ visible: false, x: 0, y: 0 });
|
|
2062
|
+
};
|
|
2063
|
+
document.addEventListener('mousedown', handleMouseDownOutside, true);
|
|
2064
|
+
return () => {
|
|
2065
|
+
document.removeEventListener('mousedown', handleMouseDownOutside, true);
|
|
2066
|
+
};
|
|
2067
|
+
}, []);
|
|
2068
|
+
const handleContextMenu = useCallback((e) => {
|
|
2069
|
+
e.preventDefault();
|
|
2070
|
+
if (selectionStart && selectionEnd) {
|
|
2071
|
+
setContextMenu({ visible: true, x: e.clientX, y: e.clientY });
|
|
2072
|
+
}
|
|
2073
|
+
}, [selectionStart, selectionEnd]);
|
|
2074
|
+
const closeContextMenu = useCallback(() => {
|
|
2075
|
+
setContextMenu({ visible: false, x: 0, y: 0 });
|
|
2076
|
+
}, []);
|
|
2077
|
+
const handleCopy = useCallback(() => {
|
|
2078
|
+
const text = getSelectedData();
|
|
2079
|
+
navigator.clipboard.writeText(text).catch(console.error);
|
|
2080
|
+
closeContextMenu();
|
|
2081
|
+
}, [getSelectedData, closeContextMenu]);
|
|
2082
|
+
const handlePaste = useCallback(() => {
|
|
2083
|
+
if (!selectionStart || !selectionEnd)
|
|
2084
|
+
return;
|
|
2085
|
+
navigator.clipboard.readText().then((text) => {
|
|
2086
|
+
if (!text)
|
|
2087
|
+
return;
|
|
2088
|
+
const rows = text
|
|
2089
|
+
.replace(/\r\n/g, '\n')
|
|
2090
|
+
.replace(/\r/g, '\n')
|
|
2091
|
+
.split('\n')
|
|
2092
|
+
.filter((row, idx, arr) => !(idx === arr.length - 1 && row === ''))
|
|
2093
|
+
.map(row => row.split('\t'));
|
|
2094
|
+
const startRow = Math.min(selectionStart.row, selectionEnd.row);
|
|
2095
|
+
const startCol = Math.min(selectionStart.col, selectionEnd.col);
|
|
2096
|
+
if (onPaste) {
|
|
2097
|
+
onPaste(startRow, startCol, rows);
|
|
2098
|
+
}
|
|
2099
|
+
else if (onCellEdit) {
|
|
2100
|
+
rows.forEach((row, rIdx) => {
|
|
2101
|
+
row.forEach((cellValue, cIdx) => {
|
|
2102
|
+
const targetRow = startRow + rIdx;
|
|
2103
|
+
const targetCol = startCol + cIdx;
|
|
2104
|
+
if (targetRow < data.length && targetCol < columns.length) {
|
|
2105
|
+
const col = columns[targetCol];
|
|
2106
|
+
if (col.editable !== false) {
|
|
2107
|
+
let parsedValue = cellValue;
|
|
2108
|
+
if (col.dataType === 'number') {
|
|
2109
|
+
const num = parseFloat(cellValue.replace(/,/g, ''));
|
|
2110
|
+
parsedValue = isNaN(num) ? 0 : num;
|
|
2111
|
+
}
|
|
2112
|
+
onCellEdit(targetRow, col.key, parsedValue);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
}).catch(console.error);
|
|
2119
|
+
closeContextMenu();
|
|
2120
|
+
}, [selectionStart, selectionEnd, onPaste, onCellEdit, data, columns, closeContextMenu]);
|
|
2121
|
+
const handleDelete = useCallback(() => {
|
|
2122
|
+
if (!selectionStart || !selectionEnd || !onCellEdit)
|
|
2123
|
+
return;
|
|
2124
|
+
const minRow = Math.min(selectionStart.row, selectionEnd.row);
|
|
2125
|
+
const maxRow = Math.max(selectionStart.row, selectionEnd.row);
|
|
2126
|
+
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
|
2127
|
+
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
|
2128
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
2129
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
2130
|
+
const col = columns[c];
|
|
2131
|
+
if (col.editable !== false) {
|
|
2132
|
+
onCellEdit(r, col.key, '');
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
closeContextMenu();
|
|
2137
|
+
}, [selectionStart, selectionEnd, onCellEdit, columns, closeContextMenu]);
|
|
2138
|
+
const handleClearSelection = useCallback(() => {
|
|
2139
|
+
setSelectionStart(null);
|
|
2140
|
+
setSelectionEnd(null);
|
|
2141
|
+
closeContextMenu();
|
|
2142
|
+
}, [closeContextMenu]);
|
|
2143
|
+
const hasSelection = selectionStart !== null && selectionEnd !== null;
|
|
2144
|
+
const selectionCellCount = useMemo(() => {
|
|
2145
|
+
if (!selectionStart || !selectionEnd)
|
|
2146
|
+
return 0;
|
|
2147
|
+
const rows = Math.abs(selectionEnd.row - selectionStart.row) + 1;
|
|
2148
|
+
const cols = Math.abs(selectionEnd.col - selectionStart.col) + 1;
|
|
2149
|
+
return rows * cols;
|
|
2150
|
+
}, [selectionStart, selectionEnd]);
|
|
2151
|
+
useEffect(() => {
|
|
2152
|
+
const container = containerRef.current;
|
|
2153
|
+
if (!container)
|
|
2154
|
+
return;
|
|
2155
|
+
const handleMouseUp = () => {
|
|
2156
|
+
if (isSelecting) {
|
|
2157
|
+
handleCellMouseUp();
|
|
2158
|
+
}
|
|
2159
|
+
};
|
|
2160
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
2161
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
2162
|
+
return () => {
|
|
2163
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
2164
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
2165
|
+
};
|
|
2166
|
+
}, [isSelecting, handleCellMouseUp, handleKeyDown]);
|
|
2167
|
+
return (jsxs(TableOuterWrapper, { ref: outerRef, className: className, tabIndex: 0, onContextMenu: handleContextMenu, children: [jsx(TableWrapper, { children: jsx(TableContainer, { ref: containerRef, maxHeight: maxHeight, children: jsxs(StyledTable, { children: [jsx("colgroup", { children: columns.map((col) => (jsx("col", { style: col.width ? { width: col.width } : undefined }, String(col.key)))) }), jsx(TableHeader, { columns: columns, sortColumn: sortColumn ?? undefined, sortDirection: sortDirection, onSort: handleSort }), jsx(TableBody, { columns: columns, data: sortedData, rowHeight: rowHeight, onCellEdit: onCellEdit, selectionStart: enableRowSelection ? null : selectionStart, selectionEnd: enableRowSelection ? null : selectionEnd, editingCell: enableRowSelection ? null : editingCell, editStartValue: editStartValue, editToken: editToken, onCellMouseDown: enableRowSelection ? undefined : handleCellMouseDown, onCellMouseEnter: enableRowSelection ? undefined : handleCellMouseEnter, onCellMouseUp: enableRowSelection ? undefined : handleCellMouseUp, enableRowSelection: enableRowSelection, selectedRowIndex: selectedRowIndex, hoveredRowIndex: hoveredRowIndex ?? undefined, onRowClick: (rowIndex) => onRowClick?.(rowIndex, sortedData[rowIndex]), onRowHover: setHoveredRowIndex })] }) }) }), maxHeight && (jsxs(ScrollContainer, { children: [jsx(ScrollButton, { position: "top", onClick: () => scroll(-100), children: jsx(ChevronUp, {}) }), jsx(ScrollButton, { position: "bottom", onClick: () => scroll(100), children: jsx(ChevronDown, {}) })] })), contextMenu.visible && (jsxs(Fragment, { children: [jsx(ContextMenuOverlay, { onClick: closeContextMenu }), jsxs(ContextMenu, { x: contextMenu.x, y: contextMenu.y, children: [jsxs(ContextMenuItem, { onClick: handleCopy, disabled: !hasSelection, children: [jsx(Copy, { size: 14 }), "\uBCF5\uC0AC", jsx("span", { className: "shortcut", children: "\u2318C" })] }), jsxs(ContextMenuItem, { onClick: handlePaste, disabled: !hasSelection, children: [jsx(ClipboardPaste, { size: 14 }), "\uBD99\uC5EC\uB123\uAE30", jsx("span", { className: "shortcut", children: "\u2318V" })] }), jsx(ContextMenuDivider, {}), jsxs(ContextMenuItem, { onClick: handleDelete, disabled: !hasSelection || !onCellEdit, children: [jsx(Trash2, { size: 14 }), "\uC0AD\uC81C", jsx("span", { className: "shortcut", children: "Del" })] }), jsx(ContextMenuDivider, {}), jsxs(ContextMenuItem, { onClick: handleClearSelection, disabled: !hasSelection, children: [jsx(XCircle, { size: 14 }), "\uC120\uD0DD \uD574\uC81C", jsx("span", { className: "shortcut", children: "Esc" })] }), hasSelection && (jsxs(Fragment, { children: [jsx(ContextMenuDivider, {}), jsxs(ContextMenuItem, { disabled: true, style: { fontSize: '11px', color: '#9ca3af' }, children: [selectionCellCount, "\uAC1C \uC140 \uC120\uD0DD\uB428"] })] }))] })] }))] }));
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
export { Button, CheckBox, Dropdown, Input, Sidebar, TabBar, Table, colors$1 as colors, typography };
|
|
1361
2171
|
//# sourceMappingURL=index.esm.js.map
|