@team-monolith/cds 1.60.2 → 1.62.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.
@@ -2,18 +2,18 @@ import React from "react";
2
2
  import { ImageProps } from "../../../components/InsertImageDialog";
3
3
  export type SelectBoxType = "primary" | "success" | "danger";
4
4
  /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
5
- export declare function SelectBoxComponent(props: {
6
- className?: string;
7
- indexClassName?: string;
8
- contentClassName?: string;
5
+ export declare const SelectBoxComponent: React.ForwardRefExoticComponent<{
6
+ className?: string | undefined;
7
+ indexClassName?: string | undefined;
8
+ contentClassName?: string | undefined;
9
9
  /** SelectBox의 스타일 타입. 전달하지 않으면 기본 스타일이 적용됩니다. */
10
- type?: SelectBoxType;
10
+ type?: SelectBoxType | undefined;
11
11
  /** 문제 번호 박스 내부를 그립니다. */
12
12
  index: React.ReactNode;
13
- image?: ImageProps | null;
13
+ image?: ImageProps | null | undefined;
14
14
  text: React.ReactNode;
15
15
  /** SelectBox content 우측에 표시될 부분 */
16
16
  endIcon?: React.ReactNode;
17
- /** 박스를 클릭할 때 실행될 콜백. 존재하지 않으면 cursor: pointer가 적용되지 않습니다. */
18
- onClick?: () => void;
19
- }): import("@emotion/react/jsx-runtime").JSX.Element;
17
+ /** 박스를 클릭할 때 실행될 콜백. 전달하지 않으면 cursor: pointer가 적용되지 않습니다. */
18
+ onClick?: (() => void) | undefined;
19
+ } & Partial<React.HTMLAttributes<HTMLDivElement>> & React.RefAttributes<HTMLDivElement>>;
@@ -1,7 +1,19 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
13
  /** @jsxImportSource @emotion/react */
3
14
  import { css, useTheme } from "@emotion/react";
4
15
  import styled from "@emotion/styled";
16
+ import { forwardRef } from "react";
5
17
  const TYPE_TO_CONTAINER_STYLE = (theme, type) => ({
6
18
  primary: css `
7
19
  background: ${theme.color.container.blueContainer};
@@ -37,17 +49,17 @@ const TYPE_TO_INDEX_STYLE = (theme, type) => ({
37
49
  `,
38
50
  })[type];
39
51
  /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
40
- export function SelectBoxComponent(props) {
41
- const { className, indexClassName, contentClassName, type, index, image, text, endIcon, onClick, } = props;
52
+ export const SelectBoxComponent = forwardRef(function SelectBoxComponent(props, ref) {
53
+ const { className, indexClassName, contentClassName, type, index, image, text, endIcon, onClick } = props, restProps = __rest(props, ["className", "indexClassName", "contentClassName", "type", "index", "image", "text", "endIcon", "onClick"]);
42
54
  const theme = useTheme();
43
- return (_jsxs(Container, Object.assign({ className: className, css: type ? TYPE_TO_CONTAINER_STYLE(theme, type) : undefined, onClick: onClick }, { children: [_jsx(Index, Object.assign({ className: indexClassName, css: type ? TYPE_TO_INDEX_STYLE(theme, type) : undefined }, { children: index })), _jsxs(Content, Object.assign({ className: contentClassName }, { children: [image && (_jsx("img", { src: image.src, alt: image.altText, css: css `
55
+ return (_jsxs(Container, Object.assign({ className: className, ref: ref }, restProps, { css: type ? TYPE_TO_CONTAINER_STYLE(theme, type) : undefined, onClick: onClick }, { children: [_jsx(Index, Object.assign({ className: indexClassName, "aria-hidden": "true", css: type ? TYPE_TO_INDEX_STYLE(theme, type) : undefined }, { children: index })), _jsxs(Content, Object.assign({ className: contentClassName, "aria-hidden": "true" }, { children: [image && (_jsx("img", { "aria-hidden": "true", src: image.src, alt: image.altText, css: css `
44
56
  height: auto;
45
57
  // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
46
58
  max-width: 100%;
47
59
  width: fit-content;
48
60
  border-radius: 6px;
49
61
  ` })), text] })), endIcon] })));
50
- }
62
+ });
51
63
  const Container = styled.div(({ theme, onClick }) => css `
52
64
  ${onClick && "cursor: pointer;"}
53
65
  display: flex;
@@ -4,6 +4,8 @@ import { css } from "@emotion/react";
4
4
  import { CheckboxCircleFillIcon, CheckFillIcon, CloseCircleFillIcon, } from "../../../../../icons";
5
5
  import { SelectBoxComponent } from "./SelectBoxComponent";
6
6
  import Tag from "../../../../../components/Tag";
7
+ import { useRef } from "react";
8
+ import { useEventListener } from "usehooks-ts";
7
9
  const TYPE_TO_INDEX_ICON = (type) => ({
8
10
  primary: (_jsx(CheckFillIcon, { css: css `
9
11
  width: 12px;
@@ -27,9 +29,28 @@ export function SelectBoxView(props) {
27
29
  ? "success"
28
30
  : "danger"
29
31
  : undefined;
32
+ const boxRef = useRef(null);
33
+ const handleKeyDown = (e) => {
34
+ // onClick이 없으면 아무것도 하지 않음.
35
+ if (!onClick) {
36
+ return;
37
+ }
38
+ // tab을 통해 focus된 상태가 아니면 아무것도 하지 않음.
39
+ if (document.activeElement !== boxRef.current) {
40
+ return;
41
+ }
42
+ // spacebar 또는 엔터를 누르면 onClick 실행
43
+ if (e.key === " " || e.key === "Enter") {
44
+ e.preventDefault();
45
+ onClick();
46
+ }
47
+ };
48
+ useEventListener("keydown", handleKeyDown);
49
+ // text가 있으면 text, 없으면 image의 altText, 없으면 index를 aria-label로 사용
50
+ const ariaText = text || (image && image.altText) || index.toString() + "번 선택지";
30
51
  return (_jsx(SelectBoxComponent, { css: css `
31
52
  width: 100%;
32
- `, type: selectBoxType, index: selectBoxType ? TYPE_TO_INDEX_ICON(selectBoxType) : index, image: image, text: text, onClick: onClick,
53
+ `, ref: boxRef, role: onClick ? "checkbox" : undefined, "aria-checked": isSelected, "aria-label": onClick ? ariaText + (isSelected ? " 선택됨" : " 선택하기") : ariaText, tabIndex: onClick ? 0 : -1, type: selectBoxType, index: selectBoxType ? TYPE_TO_INDEX_ICON(selectBoxType) : index, image: image, text: text, onClick: onClick,
33
54
  // 선택되지 않았으나 정답일 때 정답 태그를 표시
34
55
  endIcon: isAnswer &&
35
56
  !isSelected && (_jsx(Tag, { label: "\uC815\uB2F5", icon: _jsx(CheckboxCircleFillIcon, {}), color: "green", css: css `
@@ -7,12 +7,12 @@ export declare const SelectBoxClasses: {
7
7
  };
8
8
  export type SelectBoxType = "primary" | "normal";
9
9
  /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
10
- export declare function SelectBoxComponent(props: {
11
- className?: string;
10
+ export declare const SelectBoxComponent: import("react").ForwardRefExoticComponent<{
11
+ className?: string | undefined;
12
12
  type: SelectBoxType;
13
13
  index: React.ReactNode;
14
- image?: ImageProps | null;
14
+ image?: ImageProps | null | undefined;
15
15
  text: React.ReactNode;
16
16
  /** 박스를 클릭할 때 실행될 콜백. 존재하지 않으면 cursor: pointer가 적용되지 않습니다. */
17
- onClick?: () => void;
18
- }): import("@emotion/react/jsx-runtime").JSX.Element;
17
+ onClick?: (() => void) | undefined;
18
+ } & Partial<import("react").HTMLAttributes<HTMLDivElement>> & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,7 +1,19 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
13
  /** @jsxImportSource @emotion/react */
3
14
  import { css, useTheme } from "@emotion/react";
4
15
  import styled from "@emotion/styled";
16
+ import { forwardRef } from "react";
5
17
  export const SelectBoxClasses = {
6
18
  container: "SheetSelectNode-SelectBox-container",
7
19
  index: "SheetSelectNode-SelectBox-index",
@@ -32,17 +44,17 @@ const TYPE_TO_INDEX_STYLE = (theme, type) => ({
32
44
  `,
33
45
  })[type];
34
46
  /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
35
- export function SelectBoxComponent(props) {
36
- const { className, type, index, image, text, onClick } = props;
47
+ export const SelectBoxComponent = forwardRef(function SelectBoxComponent(props, ref) {
48
+ const { className, type, index, image, text, onClick } = props, restProps = __rest(props, ["className", "type", "index", "image", "text", "onClick"]);
37
49
  const theme = useTheme();
38
- return (_jsxs(Container, Object.assign({ className: `${className} ${SelectBoxClasses.container}`, css: TYPE_TO_CONTAINER_STYLE(theme, type), onClick: onClick }, { children: [_jsx(Index, Object.assign({ className: SelectBoxClasses.index, css: TYPE_TO_INDEX_STYLE(theme, type) }, { children: index })), _jsxs(Content, Object.assign({ className: SelectBoxClasses.content }, { children: [image && (_jsx("img", { src: image.src, alt: image.altText, css: css `
50
+ return (_jsxs(Container, Object.assign({ className: `${className} ${SelectBoxClasses.container}`, ref: ref }, restProps, { css: TYPE_TO_CONTAINER_STYLE(theme, type), onClick: onClick }, { children: [_jsx(Index, Object.assign({ className: SelectBoxClasses.index, "aria-hidden": "true", css: TYPE_TO_INDEX_STYLE(theme, type) }, { children: index })), _jsxs(Content, Object.assign({ className: SelectBoxClasses.content, "aria-hidden": "true" }, { children: [image && (_jsx("img", { "aria-hidden": "true", src: image.src, alt: image.altText, css: css `
39
51
  height: auto;
40
52
  // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
41
53
  max-width: 100%;
42
54
  width: fit-content;
43
55
  border-radius: 6px;
44
56
  ` })), text] }))] })));
45
- }
57
+ });
46
58
  const Container = styled.div(({ onClick }) => css `
47
59
  ${onClick && "cursor: pointer;"}
48
60
  display: flex;
@@ -3,6 +3,8 @@ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
3
3
  import { css } from "@emotion/react";
4
4
  import { SelectBoxComponent } from "./SelectBoxComponent";
5
5
  import { CheckFillIcon } from "../../../../../../icons";
6
+ import { useRef } from "react";
7
+ import { useEventListener } from "usehooks-ts";
6
8
  function getIndexIcon(type, index) {
7
9
  return {
8
10
  primary: (_jsx(CheckFillIcon, { css: css `
@@ -16,7 +18,26 @@ function getIndexIcon(type, index) {
16
18
  export function SelectBoxView(props) {
17
19
  const { index, isSelected, image, text, onClick } = props;
18
20
  const selectBoxType = isSelected ? "primary" : "normal";
21
+ const boxRef = useRef(null);
22
+ const handleKeyDown = (e) => {
23
+ // onClick이 없으면 아무것도 하지 않음.
24
+ if (!onClick) {
25
+ return;
26
+ }
27
+ // tab을 통해 focus된 상태가 아니면 아무것도 하지 않음.
28
+ if (document.activeElement !== boxRef.current) {
29
+ return;
30
+ }
31
+ // spacebar 또는 엔터를 누르면 onClick 실행
32
+ if (e.key === " " || e.key === "Enter") {
33
+ e.preventDefault();
34
+ onClick();
35
+ }
36
+ };
37
+ useEventListener("keydown", handleKeyDown);
38
+ // text가 있으면 text, 없으면 image의 altText, 없으면 index를 aria-label로 사용
39
+ const ariaText = text || (image && image.altText) || index.toString() + "번 선택지";
19
40
  return (_jsx(SelectBoxComponent, { css: css `
20
41
  width: 100%;
21
- `, type: selectBoxType, index: getIndexIcon(selectBoxType, index), image: image, text: text, onClick: onClick }));
42
+ `, ref: boxRef, role: onClick ? "checkbox" : undefined, "aria-checked": isSelected, "aria-label": onClick ? ariaText + (isSelected ? " 선택됨" : " 선택하기") : ariaText, tabIndex: onClick ? 0 : -1, type: selectBoxType, index: getIndexIcon(selectBoxType, index), image: image, text: text, onClick: onClick }));
22
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.60.2",
3
+ "version": "1.62.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,
@@ -21,7 +21,8 @@
21
21
  "react-hook-form": "^7.48.2",
22
22
  "remixicon": "^3.4.0",
23
23
  "typescript": "^4.9.5",
24
- "uid": "^2.0.2"
24
+ "uid": "^2.0.2",
25
+ "usehooks-ts": "^2.9.1"
25
26
  },
26
27
  "files": [
27
28
  "dist/**/*",