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