@lunit/oui 3.0.1 → 3.1.0

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.
@@ -47,5 +47,4 @@ const presetMap = {
47
47
  label: 'Requested',
48
48
  },
49
49
  };
50
- // eslint-disable-next-line import/prefer-default-export
51
50
  export { presetMap };
@@ -31,9 +31,7 @@ const Dropdown = (({ children, placeholder, value, error, helperMsg, onChange, s
31
31
  setTimeout(() => {
32
32
  document.activeElement.blur();
33
33
  }, 0);
34
- }, onChange: handleChange, error: !!error, open:
35
- // eslint-disable-next-line no-nested-ternary
36
- typeof otherProps.open !== 'undefined'
34
+ }, onChange: handleChange, error: !!error, open: typeof otherProps.open !== 'undefined'
37
35
  ? otherProps.open
38
36
  : otherProps.disabled
39
37
  ? false
@@ -13,9 +13,7 @@ const IconDropdown = ({ open, disabled, children, ...otherProps }) => {
13
13
  cursor: 'pointer',
14
14
  transform: 'scaleY(-1)',
15
15
  }
16
- : undefined })), open:
17
- // eslint-disable-next-line no-nested-ternary
18
- typeof open !== 'undefined' ? open : disabled ? false : isOpen, disabled: disabled, multiple: false, onClick: () => {
16
+ : undefined })), open: typeof open !== 'undefined' ? open : disabled ? false : isOpen, disabled: disabled, multiple: false, onClick: () => {
19
17
  if (!disabled)
20
18
  setOpen(!isOpen);
21
19
  }, MenuProps: {
@@ -1,5 +1,4 @@
1
1
  import { styled, Typography } from '@mui/material';
2
- // eslint-disable-next-line import/prefer-default-export
3
2
  export const StyledListItemCaption = styled(Typography)(({ theme }) => ({
4
3
  ...theme.typography.body_m1,
5
4
  color: theme.palette.neutralGrey[45],
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- /* eslint-disable @typescript-eslint/no-var-requires */
3
2
  import { Player } from '@lottiefiles/react-lottie-player';
4
3
  import { LoadingOverlay, LoadingIndicatorBox, LoadingMessage, LoadingDetails, } from './LoadingIndicator.styled';
5
4
  const DotLoadingAnimation = require('./assets/DotLoadingAnimation.json');
@@ -12,11 +12,11 @@ const NoMatchContainer = () => {
12
12
  flexDirection: 'column',
13
13
  alignItems: 'center',
14
14
  }, children: [_jsx(Typography, { variant: "h7", sx: {
15
- color: "neutralGrey.25",
15
+ color: 'neutralGrey.25',
16
16
  mt: '100px',
17
- mb: '16px'
17
+ mb: '16px',
18
18
  }, children: "Nothing matches your search" }), _jsx(Typography, { variant: "h9", sx: {
19
- color: "neutralGrey.25"
19
+ color: 'neutralGrey.25',
20
20
  }, children: "Please try again!" })] }) }));
21
21
  };
22
22
  export default NoMatchContainer;
@@ -1,5 +1,4 @@
1
1
  import { styled, RadioGroup } from '@mui/material';
2
- // eslint-disable-next-line import/prefer-default-export
3
2
  export const StyledRadioGroup = styled(RadioGroup)(({ theme }) => ({
4
3
  gap: theme.spacing(2),
5
4
  }));
@@ -26,9 +26,7 @@ const BaseTextInput = styled('input', {
26
26
  })(({ theme, large, error, leftIcon, width, type }) => {
27
27
  let resultStyle = {
28
28
  ...baseTextInputStyle(theme),
29
- width:
30
- // eslint-disable-next-line no-nested-ternary
31
- typeof width !== 'undefined' ? (typeof width === 'number' ? `${width}px` : width) : width,
29
+ width: typeof width !== 'undefined' ? (typeof width === 'number' ? `${width}px` : width) : width,
32
30
  };
33
31
  if (error) {
34
32
  resultStyle = {
@@ -64,9 +62,7 @@ const FileInputLabel = styled('label', {
64
62
  })(({ theme, large, error, leftIcon, width, disabled, readOnly }) => {
65
63
  let resultStyle = {
66
64
  ...baseTextInputStyle(theme),
67
- width:
68
- // eslint-disable-next-line no-nested-ternary
69
- typeof width !== 'undefined' ? (typeof width === 'number' ? `${width}px` : width) : width,
65
+ width: typeof width !== 'undefined' ? (typeof width === 'number' ? `${width}px` : width) : width,
70
66
  };
71
67
  if (error) {
72
68
  resultStyle = {
@@ -7,13 +7,18 @@ const Toggle = ({ value, name, checked, defaultChecked, disabled, required, onCh
7
7
  return (_jsxs(Container, { ...rootProps, className: clsx('toggle', { 'is-disabled': disabled }, rootProps.className), component: label != null ? 'label' : 'span', children: [_jsx(ToggleComponent, { disableRipple: true, value: true, name: name,
8
8
  // indeterminate가 true인 Toggle을 클릭했을 때
9
9
  // 발생하는 change event target의 checked property의 일관성을 보장함
10
- checked: indeterminate ? true : checked, defaultChecked: defaultChecked, disabled: disabled, required: required, onChange: onChange, onChangeCapture: onChangeCapture, slotProps: { input: { ...inputProps, ref: inputRef } }, focusRipple: false, toggleColor: toggleColor }), label && (_jsx(Typography, { variant: "body_m1", sx: [{
10
+ checked: indeterminate ? true : checked, defaultChecked: defaultChecked, disabled: disabled, required: required, onChange: onChange, onChangeCapture: onChangeCapture, slotProps: { input: { ...inputProps, ref: inputRef } }, focusRipple: false, toggleColor: toggleColor }), label && (_jsx(Typography, { variant: "body_m1", sx: [
11
+ {
11
12
  display: 'flex',
12
- alignItems: 'center'
13
- }, labelPlacement === 'left' ? {
14
- order: -1
15
- } : {
16
- order: 1
17
- }], children: label }))] }));
13
+ alignItems: 'center',
14
+ },
15
+ labelPlacement === 'left'
16
+ ? {
17
+ order: -1,
18
+ }
19
+ : {
20
+ order: 1,
21
+ },
22
+ ], children: label }))] }));
18
23
  };
19
24
  export default Toggle;
@@ -83,10 +83,14 @@ const UploadManager = ({ title, subTitle, files, onClose, onCancel, onRetry, })
83
83
  bottom: '20px',
84
84
  right: '20px',
85
85
  zIndex: 10,
86
- }, children: _jsxs(StyledPaper, { elevation: 1, children: [_jsxs(StyledToolbar, { children: [_jsx(Typography, { variant: "h9", children: title }), _jsx(Typography, { variant: "body1", sx: { marginLeft: '8px' }, children: subTitle }), _jsxs(StyledToolbarButtonContainer, { children: [_jsx(Tooltip, { title: expanded ? 'Minimize' : 'Maximize', children: _jsx(Box, { children: _jsx(Button, { onClick: handleExpandClick, variant: "ghost", color: "secondary", icon: _jsx(ArrowUp, { style: { color: theme.palette.neutralGrey[40] } }), size: "small", sx: [expanded ? {
87
- transform: 'rotate(0deg)'
88
- } : {
89
- transform: 'rotate(180deg)'
90
- }] }) }) }), _jsx(Tooltip, { title: "Close", children: _jsx(Box, { children: _jsx(Button, { onClick: onClose, variant: "ghost", color: "secondary", icon: _jsx(Close, { style: { color: theme.palette.neutralGrey[40] } }), size: "small" }) }) })] })] }), expanded && _jsx(Divider, { orientation: "horizontal" }), _jsx(StyledCollapse, { in: expanded, timeout: "auto", unmountOnExit: true, children: files.map((download, index) => (_jsxs(Fragment, { children: [_jsx(UploadFile, { file: download, onCancel: onCancel, onRetry: onRetry }), index !== files.length - 1 && _jsx(Divider, { orientation: "horizontal" })] }, download.id))) })] }) }));
86
+ }, children: _jsxs(StyledPaper, { elevation: 1, children: [_jsxs(StyledToolbar, { children: [_jsx(Typography, { variant: "h9", children: title }), _jsx(Typography, { variant: "body1", sx: { marginLeft: '8px' }, children: subTitle }), _jsxs(StyledToolbarButtonContainer, { children: [_jsx(Tooltip, { title: expanded ? 'Minimize' : 'Maximize', children: _jsx(Box, { children: _jsx(Button, { onClick: handleExpandClick, variant: "ghost", color: "secondary", icon: _jsx(ArrowUp, { style: { color: theme.palette.neutralGrey[40] } }), size: "small", sx: [
87
+ expanded
88
+ ? {
89
+ transform: 'rotate(0deg)',
90
+ }
91
+ : {
92
+ transform: 'rotate(180deg)',
93
+ },
94
+ ] }) }) }), _jsx(Tooltip, { title: "Close", children: _jsx(Box, { children: _jsx(Button, { onClick: onClose, variant: "ghost", color: "secondary", icon: _jsx(Close, { style: { color: theme.palette.neutralGrey[40] } }), size: "small" }) }) })] })] }), expanded && _jsx(Divider, { orientation: "horizontal" }), _jsx(StyledCollapse, { in: expanded, timeout: "auto", unmountOnExit: true, children: files.map((download, index) => (_jsxs(Fragment, { children: [_jsx(UploadFile, { file: download, onCancel: onCancel, onRetry: onRetry }), index !== files.length - 1 && _jsx(Divider, { orientation: "horizontal" })] }, download.id))) })] }) }));
91
95
  };
92
96
  export default UploadManager;
@@ -52,13 +52,18 @@ export const GraphScoreIndicator = ({ value, isCutoff, isJet, }) => {
52
52
  leftPos = '20px';
53
53
  }
54
54
  }
55
- return (_jsxs(GraphScoreIndicatorContainer, { className: "analysis-bar-score-indicator", sx: [{
56
- left: leftPos
57
- }, isJet ? {
58
- transform: 'translateX(-50%) translateY(8px)'
59
- } : {
60
- transform: 'translateX(-50%)'
61
- }], isJet: isJet, children: [_jsx(Typography, { variant: "small_body_sb1", sx: { textAlign: 'center' }, children: value }), _jsx(GraphScoreIndicatorPip, {})] }));
55
+ return (_jsxs(GraphScoreIndicatorContainer, { className: "analysis-bar-score-indicator", sx: [
56
+ {
57
+ left: leftPos,
58
+ },
59
+ isJet
60
+ ? {
61
+ transform: 'translateX(-50%) translateY(8px)',
62
+ }
63
+ : {
64
+ transform: 'translateX(-50%)',
65
+ },
66
+ ], isJet: isJet, children: [_jsx(Typography, { variant: "small_body_sb1", sx: { textAlign: 'center' }, children: value }), _jsx(GraphScoreIndicatorPip, {})] }));
62
67
  };
63
68
  const GraphAxisLabelContainer = styled('span')(() => {
64
69
  return {
@@ -7,26 +7,41 @@ const FreeText = ({ componentType = 'freeText', size = 'medium', content, height
7
7
  throw new Error("The 'componentType' prop is required.");
8
8
  if (!content)
9
9
  throw new Error("The 'content' prop is required.");
10
- return (_jsx(Box, { sx: [{
10
+ return (_jsx(Box, { sx: [
11
+ {
11
12
  display: 'flex',
12
- overflowY: 'auto'
13
- }, background ? {
14
- padding: '8px 6px 8px 16px'
15
- } : {
16
- padding: 0
17
- }, background ? {
18
- borderRadius: '8px'
19
- } : {
20
- borderRadius: 0
21
- }, background ? {
22
- backgroundColor: theme.palette.neutralGrey[75]
23
- } : {
24
- backgroundColor: 'transparent'
25
- }, height ? {
26
- height: height
27
- } : {
28
- height: 'auto'
29
- }], children: _jsx(PresentationTypography, { size: size, sx: [{
13
+ overflowY: 'auto',
14
+ },
15
+ background
16
+ ? {
17
+ padding: '8px 6px 8px 16px',
18
+ }
19
+ : {
20
+ padding: 0,
21
+ },
22
+ background
23
+ ? {
24
+ borderRadius: '8px',
25
+ }
26
+ : {
27
+ borderRadius: 0,
28
+ },
29
+ background
30
+ ? {
31
+ backgroundColor: theme.palette.neutralGrey[75],
32
+ }
33
+ : {
34
+ backgroundColor: 'transparent',
35
+ },
36
+ height
37
+ ? {
38
+ height: height,
39
+ }
40
+ : {
41
+ height: 'auto',
42
+ },
43
+ ], children: _jsx(PresentationTypography, { size: size, sx: [
44
+ {
30
45
  display: 'block',
31
46
  whiteSpace: 'pre-wrap',
32
47
  overflow: 'auto',
@@ -37,15 +52,22 @@ const FreeText = ({ componentType = 'freeText', size = 'medium', content, height
37
52
  width: '6px',
38
53
  backgroundColor: theme.palette.neutralGrey[65],
39
54
  borderRadius: '6px',
55
+ },
56
+ },
57
+ background
58
+ ? {
59
+ color: '#FFFFFF',
60
+ }
61
+ : {
62
+ color: 'inherit',
63
+ },
64
+ height
65
+ ? {
66
+ height: '100%',
40
67
  }
41
- }, background ? {
42
- color: '#FFFFFF'
43
- } : {
44
- color: 'inherit'
45
- }, height ? {
46
- height: '100%'
47
- } : {
48
- height: 'auto'
49
- }], children: content }) }));
68
+ : {
69
+ height: 'auto',
70
+ },
71
+ ], children: content }) }));
50
72
  };
51
73
  export default FreeText;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Divider } from '../../components';
2
+ import { Divider } from '../../components/Divider';
3
3
  const HorizonDivider = ({ componentType = 'divider' }) => {
4
4
  if (!componentType)
5
5
  throw new Error("The 'componentType' prop is required.");
@@ -6,25 +6,27 @@ import { BaseSlider, Container } from './Threshold.styled';
6
6
  import { Score } from '../Score';
7
7
  import { Statistic } from '../Statistic';
8
8
  import { PresentationTypography } from '../presentations.styled';
9
- import { Button, Dropdown, DropdownItem } from '../../components';
9
+ import { Button } from '../../components/Button';
10
+ import { Dropdown, DropdownItem } from '../../components/Dropdown';
10
11
  import { HorizonDivider } from '../HorizonDivider';
11
12
  import { FreeText } from '../FreeText';
12
- import { baseSliderMarks, getPresetValue } from './Threshold.utils';
13
- const Threshold = ({ componentType = 'threshold', title, totalScore, totalCells, statisticItems, options, description, controlStep = 1, isLoading = false, thresholdState, onChangeDropdown, onChangeSlider, onSliderChangeCommitted, onClickUpdateConfiguration, }) => {
13
+ import { NO_LABEL_ONLY_MARK, baseSliderMarks, getLabelOnlyMarkIndex, getSliderMarks, } from './Threshold.utils';
14
+ const Threshold = ({ componentType = 'threshold', title, totalScore, totalCells, statisticItems, options, description, controlStep = 1, showDefaultMark = false, isLoading = false, thresholdState, onChangeDropdown, onChangeSlider, onSliderChangeCommitted, onClickUpdateConfiguration, }) => {
14
15
  if (!componentType)
15
16
  throw new Error("The 'componentType' prop is required.");
16
- if (!totalScore)
17
- throw new Error("The 'totalScore' prop is required.");
18
17
  if (!statisticItems.length)
19
18
  throw new Error("The 'statisticItems' prop must contain at least one item.");
20
19
  if (!options.length)
21
20
  throw new Error("The 'options' prop must contain at least one item.");
22
21
  const [isThresholdChanged, setIsThresholdChanged] = useState(false);
23
22
  const selectedOptionValue = useMemo(() => {
24
- return (options.find((option) => option.title === thresholdState?.selectedItem)?.value ||
23
+ return (options.find((option) => option.title === thresholdState?.selectedItem)?.value ??
25
24
  options[0].value);
26
25
  }, [options, thresholdState?.selectedItem]);
27
- const presetValue = getPresetValue(options, thresholdState?.selectedItem);
26
+ const sliderMarks = useMemo(() => (showDefaultMark ? getSliderMarks(selectedOptionValue) : baseSliderMarks), [showDefaultMark, selectedOptionValue]);
27
+ const labelOnlyMarkIndex = useMemo(() => showDefaultMark
28
+ ? getLabelOnlyMarkIndex(sliderMarks, selectedOptionValue)
29
+ : NO_LABEL_ONLY_MARK, [showDefaultMark, sliderMarks, selectedOptionValue]);
28
30
  const handleDropdownChange = useCallback((event) => {
29
31
  onChangeDropdown(event);
30
32
  setIsThresholdChanged(false);
@@ -37,11 +39,11 @@ const Threshold = ({ componentType = 'threshold', title, totalScore, totalCells,
37
39
  onClickUpdateConfiguration();
38
40
  setIsThresholdChanged(false);
39
41
  }, [onClickUpdateConfiguration]);
40
- return (_jsxs(Container, { children: [_jsx(Score, { ...totalScore }), _jsx(Statistic, { ...totalCells }), statisticItems.map((item) => {
42
+ return (_jsxs(Container, { children: [totalScore && _jsx(Score, { ...totalScore }), _jsx(Statistic, { ...totalCells }), statisticItems.map((item) => {
41
43
  return (_jsx(Statistic, { ...item }, uuidV4()));
42
44
  }), _jsx(HorizonDivider, { componentType: "divider" }), _jsx(PresentationTypography, { size: "medium", children: title }), _jsx(Dropdown, { value: thresholdState?.selectedItem, onChange: handleDropdownChange, sx: { width: '258px' }, children: options.map((option) => {
43
45
  return (_jsx(DropdownItem, { value: option.title, children: option.title }, option.title));
44
- }) }), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', paddingTop: '36px', gap: '6px' }, children: [_jsx(BaseSlider, { disabled: thresholdState?.isPresetSelected, step: controlStep, value: thresholdState?.sliderValue, valueLabelDisplay: "on", defaultThreshold: presetValue, marks: baseSliderMarks, onChange: handleSliderChange, onChangeCommitted: onSliderChangeCommitted }), !thresholdState?.isPresetSelected && (_jsx(Button, { id: "update-threshold-config-btn", label: "Update Configuration", sx: {
46
+ }) }), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', paddingTop: '36px', gap: '6px' }, children: [_jsx(BaseSlider, { disabled: thresholdState?.isPresetSelected, step: controlStep, value: thresholdState?.sliderValue, valueLabelDisplay: "on", marks: sliderMarks, labelOnlyMarkIndex: labelOnlyMarkIndex, onChange: handleSliderChange, onChangeCommitted: onSliderChangeCommitted }), !thresholdState?.isPresetSelected && (_jsx(Button, { id: "update-threshold-config-btn", label: "Update Configuration", sx: {
45
47
  width: '100%',
46
48
  }, disabled: !isThresholdChanged || isLoading, onClick: handleUpdateConfiguration, loading: isLoading })), thresholdState?.isPresetSelected && description?.preset && (_jsx(FreeText, { componentType: "freeText", size: "small-dimmed", content: description.preset })), !thresholdState?.isPresetSelected && description?.user && (_jsx(FreeText, { componentType: "freeText", size: "small-dimmed", content: description.user }))] })] }));
47
49
  };
@@ -1,4 +1,5 @@
1
1
  import { Box, Slider, styled } from '@mui/material';
2
+ import { NO_LABEL_ONLY_MARK } from './Threshold.utils';
2
3
  import theme from '../../theme';
3
4
  export const Container = styled(Box)({
4
5
  display: 'flex',
@@ -6,67 +7,64 @@ export const Container = styled(Box)({
6
7
  gap: '12px',
7
8
  });
8
9
  export const BaseSlider = styled(Slider, {
9
- shouldForwardProp: (prop) => prop !== 'defaultThreshold',
10
- })(({ defaultThreshold, marks, disabled }) => {
11
- let markIndex = 0;
12
- if (Array.isArray(marks) && marks.length > 0) {
13
- markIndex = marks.findIndex((mark) => mark.value === defaultThreshold);
14
- }
15
- const defaultThresClass = `&:nth-of-type(${markIndex + 4})`;
16
- const defaultLabelClass = `&:nth-of-type(${markIndex + 5})`;
17
- return {
18
- '& .MuiSlider-rail': {
19
- background: theme.palette.neutralGrey[75],
20
- height: '8px',
10
+ shouldForwardProp: (prop) => prop !== 'labelOnlyMarkIndex',
11
+ })(({ disabled, labelOnlyMarkIndex = NO_LABEL_ONLY_MARK }) => ({
12
+ '& .MuiSlider-rail': {
13
+ background: theme.palette.neutralGrey[75],
14
+ height: '8px',
15
+ },
16
+ '& .MuiSlider-track': {
17
+ display: 'none',
18
+ },
19
+ '& .MuiSlider-thumb': {
20
+ background: theme.palette.neutralGrey[0],
21
+ '&:hover': {
22
+ boxShadow: `0px 0px 0px 8px rgba(255, 0, 0, 0.2)`,
21
23
  },
22
- '& .MuiSlider-track': {
23
- display: 'none',
24
+ '&.Mui-focusVisible': {
25
+ boxShadow: `0px 0px 0px 8px rgba(255, 0, 0, 0.2)`,
24
26
  },
25
- '& .MuiSlider-thumb': {
26
- background: theme.palette.neutralGrey[0],
27
- '&:hover': {
28
- boxShadow: `0px 0px 0px 8px rgba(255, 0, 0, 0.2)`,
29
- },
30
- '&.Mui-focusVisible': {
31
- boxShadow: `0px 0px 0px 8px rgba(255, 0, 0, 0.2)`,
32
- },
27
+ },
28
+ '& .MuiSlider-mark': {
29
+ background: theme.palette.neutralGrey[40],
30
+ height: '6px',
31
+ width: '6px',
32
+ borderRadius: '50%',
33
+ },
34
+ '& .MuiSlider-markLabel': {
35
+ fontFamily: 'Pretendard,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"',
36
+ fontSize: '8px',
37
+ fontStyle: 'normal',
38
+ fontWeight: '500',
39
+ lineHeight: '12px',
40
+ },
41
+ '& .MuiSlider-valueLabel': {
42
+ fontFamily: 'Pretendard,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"',
43
+ fontSize: '12px',
44
+ fontWeight: '700',
45
+ lineHeight: '16px',
46
+ background: 'unset',
47
+ padding: 0,
48
+ width: 32,
49
+ height: 32,
50
+ borderRadius: '50% 50% 50% 0',
51
+ backgroundColor: disabled ? theme.palette.neutralGrey[60] : theme.palette.lunitTeal[40],
52
+ transformOrigin: 'bottom left',
53
+ transform: 'translate(50%, -100%) rotate(-45deg) scale(0)',
54
+ '&:before': { display: 'none' },
55
+ '&.MuiSlider-valueLabelOpen': {
56
+ transform: 'translate(50%, -100%) rotate(-45deg) scale(1)',
33
57
  },
34
- '& .MuiSlider-mark': {
35
- background: theme.palette.neutralGrey[40],
36
- height: '6px',
37
- width: '6px',
38
- borderRadius: '50%',
58
+ '& > *': {
59
+ transform: 'rotate(45deg)',
39
60
  },
40
- '& .MuiSlider-markLabel': {
41
- fontFamily: 'Pretendard,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"',
42
- fontSize: '8px',
43
- fontStyle: 'normal',
44
- fontWeight: '500',
45
- lineHeight: '12px',
61
+ '&.Mui-disabled': {
62
+ visibility: 'visible',
46
63
  },
47
- '& .MuiSlider-valueLabel': {
48
- fontFamily: 'Pretendard,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"',
49
- fontSize: '12px',
50
- fontWeight: '700',
51
- lineHeight: '16px',
52
- background: 'unset',
53
- padding: 0,
54
- width: 32,
55
- height: 32,
56
- borderRadius: '50% 50% 50% 0',
57
- backgroundColor: disabled ? theme.palette.neutralGrey[60] : theme.palette.lunitTeal[40],
58
- transformOrigin: 'bottom left',
59
- transform: 'translate(50%, -100%) rotate(-45deg) scale(0)',
60
- '&:before': { display: 'none' },
61
- '&.MuiSlider-valueLabelOpen': {
62
- transform: 'translate(50%, -100%) rotate(-45deg) scale(1)',
63
- },
64
- '& > *': {
65
- transform: 'rotate(45deg)',
66
- },
67
- '&.Mui-disabled': {
68
- visibility: 'visible',
69
- },
64
+ },
65
+ ...(labelOnlyMarkIndex > NO_LABEL_ONLY_MARK && {
66
+ [`& .MuiSlider-mark[data-index="${labelOnlyMarkIndex}"]`]: {
67
+ display: 'none',
70
68
  },
71
- };
72
- });
69
+ }),
70
+ }));
@@ -1,3 +1,5 @@
1
+ const DEFAULT_MARK_LABEL_PREFIX = 'Default: ';
2
+ export const NO_LABEL_ONLY_MARK = -1;
1
3
  export const baseSliderMarks = [
2
4
  { value: 0, label: '0' },
3
5
  { value: 10 },
@@ -11,24 +13,31 @@ export const baseSliderMarks = [
11
13
  { value: 90 },
12
14
  { value: 100, label: '100' },
13
15
  ];
14
- export const getPresetValue = (options, selectedItem) => {
15
- const presetValue = options.find((option) => option.title === selectedItem && option.isPreset)
16
- ?.value;
17
- return presetValue;
18
- };
19
- export const getSliderMarks = (presetValue) => {
20
- const marks = baseSliderMarks.map((mark) => {
21
- if (mark.value === presetValue) {
22
- return {
23
- ...mark,
24
- label: `Preset: ${presetValue}`,
25
- };
26
- }
27
- return mark;
16
+ /**
17
+ * Builds the slider marks with the default threshold labelled.
18
+ *
19
+ * @param defaultThreshold - Default threshold of the selected option
20
+ * @returns Marks ordered by value, including an extra mark when the default sits between intervals
21
+ */
22
+ export const getSliderMarks = (defaultThreshold) => {
23
+ const defaultLabel = `${DEFAULT_MARK_LABEL_PREFIX}${defaultThreshold}`;
24
+ const marks = baseSliderMarks.map((mark) => mark.value === defaultThreshold ? { ...mark, label: defaultLabel } : mark);
25
+ if (baseSliderMarks.some((mark) => mark.value === defaultThreshold))
26
+ return marks;
27
+ const insertIndex = marks.findIndex((mark) => mark.value > defaultThreshold);
28
+ marks.splice(insertIndex < 0 ? marks.length : insertIndex, 0, {
29
+ value: defaultThreshold,
30
+ label: defaultLabel,
28
31
  });
29
- if (!baseSliderMarks.find((mark) => mark.value === presetValue)) {
30
- const index = Math.floor(presetValue / 10);
31
- marks.splice(index + 1, 0, { value: presetValue, label: `Preset: ${presetValue}` });
32
- }
33
32
  return marks;
34
33
  };
34
+ /**
35
+ * Finds the mark that exists only to label the default threshold.
36
+ *
37
+ * @param marks - Marks built by `getSliderMarks`
38
+ * @param defaultThreshold - Default threshold of the selected option
39
+ * @returns Index of the mark whose dot should stay hidden, or `-1` when the default sits on an interval
40
+ */
41
+ export const getLabelOnlyMarkIndex = (marks, defaultThreshold) => baseSliderMarks.some((mark) => mark.value === defaultThreshold)
42
+ ? NO_LABEL_ONLY_MARK
43
+ : marks.findIndex((mark) => mark.value === defaultThreshold);
@@ -2,10 +2,10 @@ import { ReactNode } from 'react';
2
2
  import type { TooltipClasses, TooltipProps as MuiTooltipProps } from '@mui/material';
3
3
  export type Position = Pick<TooltipProps, 'placement'>;
4
4
  export type TooltipSizes = 'small' | 'large';
5
- export type TooltipSize = {
5
+ export interface TooltipSize {
6
6
  width: number;
7
7
  height: number;
8
- };
8
+ }
9
9
  export type TooltipPlacement = TooltipClasses['tooltipPlacementBottom'] | TooltipClasses['tooltipPlacementLeft'] | TooltipClasses['tooltipPlacementRight'] | TooltipClasses['tooltipPlacementTop'];
10
10
  export type ArrowSizeBySize = Record<TooltipSizes, TooltipSize>;
11
11
  export type TooltipPlacementToSize = Record<TooltipPlacement, ArrowSizeBySize>;
@@ -1,3 +1,3 @@
1
1
  import { ThresholdProps } from './Threshold.types';
2
- declare const Threshold: ({ componentType, title, totalScore, totalCells, statisticItems, options, description, controlStep, isLoading, thresholdState, onChangeDropdown, onChangeSlider, onSliderChangeCommitted, onClickUpdateConfiguration, }: ThresholdProps) => import("react/jsx-runtime").JSX.Element;
2
+ declare const Threshold: ({ componentType, title, totalScore, totalCells, statisticItems, options, description, controlStep, showDefaultMark, isLoading, thresholdState, onChangeDropdown, onChangeSlider, onSliderChangeCommitted, onClickUpdateConfiguration, }: ThresholdProps) => import("react/jsx-runtime").JSX.Element;
3
3
  export default Threshold;
@@ -9,18 +9,29 @@ export interface ThresholdOption {
9
9
  value: number;
10
10
  isPreset?: boolean;
11
11
  }
12
+ export interface ThresholdMark {
13
+ /** Position of the mark on the 0-100 intensity scale */
14
+ value: number;
15
+ /** Text rendered under the mark; marks without a label render as a dot only */
16
+ label?: string;
17
+ }
12
18
  export interface ThresholdSpec {
13
19
  componentType: 'threshold';
14
- totalScore: ScoreProps;
20
+ /** Overall score summary shown above the cell statistics; omitted when the analysis has no score */
21
+ totalScore?: ScoreProps;
15
22
  totalCells: StatisticProps;
16
23
  statisticItems: StatisticProps[];
17
24
  title?: string;
18
25
  description?: {
19
- preset: string;
20
- user: string;
26
+ /** Guidance shown while a preset option is selected */
27
+ preset?: string;
28
+ /** Guidance shown while a user-adjustable option is selected */
29
+ user?: string;
21
30
  };
22
31
  options: ThresholdOption[];
23
32
  controlStep?: number;
33
+ /** Labels the selected option's threshold on the slider, e.g. `Default: 40`; off by default */
34
+ showDefaultMark?: boolean;
24
35
  }
25
36
  export interface ThresholdProps extends ThresholdSpec {
26
37
  thresholdState?: {
@@ -35,5 +46,6 @@ export interface ThresholdProps extends ThresholdSpec {
35
46
  isLoading?: boolean;
36
47
  }
37
48
  export interface BaseSliderProps extends SliderProps {
38
- defaultThreshold: number;
49
+ /** Index of the mark that renders its label without a dot; `-1` hides nothing */
50
+ labelOnlyMarkIndex?: number;
39
51
  }
@@ -1,16 +1,18 @@
1
- import { ThresholdOption } from './Threshold.types';
2
- export declare const baseSliderMarks: ({
3
- value: number;
4
- label: string;
5
- } | {
6
- value: number;
7
- label?: undefined;
8
- })[];
9
- export declare const getPresetValue: (options: ThresholdOption[], selectedItem: string) => number;
10
- export declare const getSliderMarks: (presetValue: number) => ({
11
- value: number;
12
- label: string;
13
- } | {
14
- value: number;
15
- label?: undefined;
16
- })[];
1
+ import { ThresholdMark } from './Threshold.types';
2
+ export declare const NO_LABEL_ONLY_MARK = -1;
3
+ export declare const baseSliderMarks: ThresholdMark[];
4
+ /**
5
+ * Builds the slider marks with the default threshold labelled.
6
+ *
7
+ * @param defaultThreshold - Default threshold of the selected option
8
+ * @returns Marks ordered by value, including an extra mark when the default sits between intervals
9
+ */
10
+ export declare const getSliderMarks: (defaultThreshold: number) => ThresholdMark[];
11
+ /**
12
+ * Finds the mark that exists only to label the default threshold.
13
+ *
14
+ * @param marks - Marks built by `getSliderMarks`
15
+ * @param defaultThreshold - Default threshold of the selected option
16
+ * @returns Index of the mark whose dot should stay hidden, or `-1` when the default sits on an interval
17
+ */
18
+ export declare const getLabelOnlyMarkIndex: (marks: ThresholdMark[], defaultThreshold: number) => number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunit/oui",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "validate-branch-name": {
5
5
  "pattern": "^(main|develop)|(feature|fix|release|hotfix|qe)/.+$",
6
6
  "errorMsg": "The branch name is not correct. Please check the pattern. (ex. feature/add-something)"
@@ -8,14 +8,25 @@
8
8
  "peerDependencies": {
9
9
  "@emotion/react": "^11.11.1",
10
10
  "@emotion/styled": "^11.11.0",
11
+ "@lottiefiles/react-lottie-player": "^3.5.3",
11
12
  "@mui/icons-material": "^9.0.0",
12
13
  "@mui/lab": "^9.0.0",
13
14
  "@mui/material": "^9.0.0",
14
15
  "@mui/system": "^9.0.0",
16
+ "@mui/x-data-grid": "^9.0.0",
17
+ "@mui/x-data-grid-pro": "^9.0.0",
15
18
  "@mui/x-date-pickers": "^9.0.0",
16
19
  "react": "^18.2.0",
17
20
  "react-dom": "^18.2.0"
18
21
  },
22
+ "peerDependenciesMeta": {
23
+ "@lottiefiles/react-lottie-player": {
24
+ "optional": true
25
+ },
26
+ "@mui/x-data-grid-pro": {
27
+ "optional": true
28
+ }
29
+ },
19
30
  "dependencies": {
20
31
  "@date-io/date-fns": "^3.2.1",
21
32
  "@lunit/design-system-icons": "2.1.0",
@@ -31,6 +42,7 @@
31
42
  },
32
43
  "scripts": {
33
44
  "prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
45
+ "test": "jest",
34
46
  "build": "npm run build:transpile",
35
47
  "build:transpile": "tsc -p tsconfig.build.json",
36
48
  "build:cjs": "NODE_ENV=production webpack -c config/cjs.config.js",
@@ -82,7 +94,7 @@
82
94
  "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
83
95
  },
84
96
  "transformIgnorePatterns": [
85
- "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$",
97
+ "[/\\\\]node_modules[/\\\\](?!uuid[/\\\\]).+\\.(js|jsx|mjs|cjs|ts|tsx)$",
86
98
  "^.+\\.module\\.(css|sass|scss)$"
87
99
  ],
88
100
  "modulePaths": [],
@@ -120,7 +132,7 @@
120
132
  "@emotion/react": "11.11.1",
121
133
  "@emotion/styled": "11.11.0",
122
134
  "@lottiefiles/react-lottie-player": "^3.5.3",
123
- "@lunit/eslint-config": "^2.0.0",
135
+ "@lunit/eslint-config": "^2.0.1",
124
136
  "@mui/icons-material": "9.1.0",
125
137
  "@mui/lab": "9.0.0-beta.3",
126
138
  "@mui/material": "9.1.0",
@@ -164,7 +176,7 @@
164
176
  "css-minimizer-webpack-plugin": "^8.0.0",
165
177
  "dotenv": "^10.0.0",
166
178
  "dotenv-expand": "^5.1.0",
167
- "eslint": "^8.3.0",
179
+ "eslint": "^9.39.5",
168
180
  "eslint-config-prettier": "^9.1.0",
169
181
  "eslint-config-react-app": "^7.0.1",
170
182
  "eslint-plugin-prettier": "^5.1.0",
@@ -176,6 +188,7 @@
176
188
  "husky": "^8.0.0",
177
189
  "identity-obj-proxy": "^3.0.0",
178
190
  "jest": "^30.3.0",
191
+ "jest-environment-jsdom": "^30.4.1",
179
192
  "jest-resolve": "^27.4.2",
180
193
  "jest-watch-typeahead": "^1.0.0",
181
194
  "lint-staged": "^15.2.0",