@egov3/system-design 1.0.3 → 1.0.5

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.
@@ -0,0 +1,101 @@
1
+ import React from "react";
2
+ import { render, fireEvent } from "@testing-library/react";
3
+ import "@testing-library/jest-dom";
4
+
5
+ import { InputField } from "~baseComponents";
6
+
7
+ describe("InputField", () => {
8
+ it("(1) Should render without crashing", () => {
9
+ const { getByPlaceholderText } = render(
10
+ <InputField
11
+ id={"testRenderId"}
12
+ labelText={""}
13
+ ariaLabel={"test ariaLabel"}
14
+ />
15
+ );
16
+ expect(getByPlaceholderText("")).toBeInTheDocument();
17
+ });
18
+
19
+ it("(2) Should render with the correct placeholder", () => {
20
+ const placeholder = "Enter text";
21
+ const { getByPlaceholderText } = render(
22
+ <InputField
23
+ id={"testPlaceholderId"}
24
+ labelText={""}
25
+ ariaLabel={"test ariaLabel"}
26
+ placeholder={placeholder}
27
+ />
28
+ );
29
+ expect(getByPlaceholderText(placeholder)).toBeInTheDocument();
30
+ });
31
+
32
+ it("(3) Should call onFocus and onBlur handlers", () => {
33
+ const onFocus = jest.fn();
34
+ const onBlur = jest.fn();
35
+ const { getByPlaceholderText } = render(
36
+ <InputField
37
+ id={"testOnBlurId"}
38
+ labelText={""}
39
+ ariaLabel={"test ariaLabel"}
40
+ placeholder="test"
41
+ onFocus={onFocus}
42
+ onBlur={onBlur}
43
+ />
44
+ );
45
+ const input = getByPlaceholderText("test");
46
+ fireEvent.focus(input);
47
+ expect(onFocus).toHaveBeenCalled();
48
+ fireEvent.blur(input);
49
+ expect(onBlur).toHaveBeenCalled();
50
+ });
51
+
52
+ it("(4) Should render inputLeftIcon if provided", () => {
53
+ const leftIcon = <span data-testid="left-icon">Icon</span>;
54
+ const { getByTestId } = render(
55
+ <InputField
56
+ id={"testLeftId"}
57
+ labelText={""}
58
+ ariaLabel={"test ariaLabel"}
59
+ inputLeftIcon={leftIcon}
60
+ />
61
+ );
62
+ expect(getByTestId("left-icon")).toBeInTheDocument();
63
+ });
64
+
65
+ it.skip("(5) Should render and clears value when clear icon is clicked", () => {
66
+ const onChange = jest.fn();
67
+ const value = "test value";
68
+ const { getByPlaceholderText, getByTestId } = render(
69
+ <InputField
70
+ id={"testId"}
71
+ labelText={""}
72
+ ariaLabel={"test ariaLabel"}
73
+ placeholder="test"
74
+ value={value}
75
+ onChange={onChange}
76
+ isClearable
77
+ />
78
+ );
79
+ const input = getByPlaceholderText("test");
80
+ expect(input).toHaveValue(value);
81
+ const clearIcon = getByTestId("Icons_CLEAR");
82
+ fireEvent.click(clearIcon);
83
+ expect(onChange).toHaveBeenCalledWith(
84
+ expect.objectContaining({ target: { value: "" } })
85
+ );
86
+ });
87
+
88
+ it("(6) Should apply focused class on focus", () => {
89
+ const { getByPlaceholderText, container } = render(
90
+ <InputField
91
+ id={"testFocusedId"}
92
+ labelText={""}
93
+ ariaLabel={"test ariaLabel"}
94
+ placeholder="test"
95
+ />
96
+ );
97
+ const input = getByPlaceholderText("test");
98
+ fireEvent.focus(input);
99
+ expect(container.firstChild).toHaveClass("input--onfocus");
100
+ });
101
+ });
package/index.tsx CHANGED
@@ -1,3 +1,3 @@
1
- import { Button } from "src/baseComponents/Button";
1
+ import { InputField } from "src/baseComponents/InputField";
2
2
 
3
- export { Button };
3
+ export { InputField };
package/jest.config.ts CHANGED
@@ -18,7 +18,6 @@ const customJestConfig = {
18
18
  testEnvironment: 'jsdom',
19
19
  moduleNameMapper: {
20
20
  '@/(.*)': '<rootDir>/$1',
21
- '~app/(.*)': '<rootDir>/src/app/$1',
22
21
  '~constants/(.*)': '<rootDir>/src/constants/$1',
23
22
  '~customHooks/(.*)': '<rootDir>/src/customHooks/$1',
24
23
  '~customMock/(.*)': '<rootDir>/__tests__/customMock/$1',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@egov3/system-design",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "main": "index.tsx",
6
6
  "private": false,
@@ -9,6 +9,9 @@
9
9
  },
10
10
  "scripts": {
11
11
  "lint": "next lint",
12
+ "test": "jest",
13
+ "test:unit:update": "jest -u",
14
+ "test:unit:coverage": "jest --coverage",
12
15
  "lint-staged": "lint-staged",
13
16
  "type-check": "tsc --project tsconfig.json --pretty --noEmit && echo ",
14
17
  "prepare": "cross-env NODE_ENV=development yarn prepare:dev",
@@ -20,26 +23,61 @@
20
23
  "license": "ISC",
21
24
  "description": "system-design components for new egov 3.0",
22
25
  "dependencies": {
23
- "next": "^15.0.0"
26
+ "@types/js-cookie": "^3.0.6",
27
+ "@types/qrcode": "^1.5.5",
28
+ "critters": "^0.0.24",
29
+ "js-cookie": "^3.0.5",
30
+ "next": "^14.1.0",
31
+ "react": "^18.3.1",
32
+ "react-dom": "^18.3.1",
33
+ "ts-jest": "^29.1.2"
24
34
  },
25
35
  "devDependencies": {
26
- "@babel/preset-env": "^7.25.9",
27
- "@babel/preset-typescript": "^7.25.9",
36
+ "@babel/plugin-syntax-jsx": "^7.23.3",
37
+ "@babel/preset-env": "^7.23.9",
38
+ "@babel/preset-react": "^7.23.3",
39
+ "@babel/preset-typescript": "^7.23.3",
28
40
  "@chromatic-com/storybook": "^1.3.2",
41
+ "@commitlint/cli": "^18.6.1",
42
+ "@commitlint/config-conventional": "^18.6.2",
43
+ "@next/eslint-plugin-next": "^14.1.0",
29
44
  "@storybook/addon-essentials": "^8.0.8",
30
45
  "@storybook/addon-interactions": "^8.0.8",
31
46
  "@storybook/addon-links": "^8.0.8",
32
47
  "@storybook/addon-onboarding": "^8.0.8",
48
+ "@storybook/blocks": "^8.0.8",
33
49
  "@storybook/nextjs": "^8.0.8",
34
50
  "@storybook/react": "^8.1.11",
35
51
  "@storybook/test": "^8.0.8",
52
+ "@testing-library/dom": "^10.3.2",
53
+ "@testing-library/jest-dom": "^6.4.8",
54
+ "@testing-library/react": "^14.3.1",
55
+ "@types/jest": "^29.5.12",
36
56
  "@types/node": "^20.11.19",
37
57
  "@types/react": "^18.3.3",
38
58
  "@types/react-dom": "^18.3.0",
39
- "babel-jest": "^29.7.0",
59
+ "@types/react-test-renderer": "^18.0.7",
60
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
61
+ "@typescript-eslint/parser": "^6.21.0",
62
+ "commitizen": "^4.3.0",
40
63
  "cross-env": "^7.0.3",
64
+ "eslint": "^8.56.0",
65
+ "eslint-config-next": "14.1.0",
66
+ "eslint-config-prettier": "^9.1.0",
67
+ "eslint-plugin-import": "^2.29.1",
68
+ "eslint-plugin-n": "^16.6.2",
69
+ "eslint-plugin-promise": "^6.1.1",
70
+ "eslint-plugin-storybook": "^0.8.0",
41
71
  "husky": "^9.0.11",
72
+ "jest": "^29.7.0",
73
+ "jest-environment-jsdom": "^29.7.0",
74
+ "lint-staged": "^15.2.2",
75
+ "npm-check-updates": "^16.14.18",
76
+ "prettier": "^3.2.5",
77
+ "react-test-renderer": "^18.2.0",
42
78
  "sass": "^1.75.0",
43
- "storybook": "^8.0.8"
79
+ "storybook": "^8.0.8",
80
+ "ts-node": "^10.9.2",
81
+ "typescript": "^5.4.0"
44
82
  }
45
83
  }
File without changes
@@ -0,0 +1,57 @@
1
+ @import '/src/styles/colors.module';
2
+ @import '/src/styles/typography.module';
3
+
4
+ .inputContainer {
5
+ display: flex;
6
+ align-items: center;
7
+ background-color: $Surface-Surface-1;
8
+ border-radius: 8px;
9
+ padding: 12px 16px;
10
+ }
11
+
12
+ .inputContainerLabeled {
13
+ display: block;
14
+ background-color: $Surface-Surface-1;
15
+ border-radius: 8px;
16
+ padding: 12px 16px;
17
+
18
+ label {
19
+ color: $Text-Secondary;
20
+ }
21
+ }
22
+
23
+ .input {
24
+ width: 100%;
25
+ border: none;
26
+ background-color: $Surface-Surface-1;
27
+
28
+ &:active,
29
+ &:focus {
30
+ outline: none;
31
+ }
32
+
33
+ &::placeholder {
34
+ color: $Text-Disabled-color;
35
+ }
36
+
37
+ &::-webkit-inner-spin-button,
38
+ &::-webkit-outer-spin-button {
39
+ -webkit-appearance: none;
40
+ margin: 0;
41
+ }
42
+ }
43
+
44
+ .clearIcon {
45
+ cursor: pointer;
46
+ }
47
+
48
+ .input--onfocus {
49
+ background-color: $Surface-Surface-3-color;
50
+ .input {
51
+ background-color: $Surface-Surface-3-color;
52
+ }
53
+ }
54
+
55
+ .input-text {
56
+ @include Body1Regular;
57
+ }
@@ -0,0 +1,108 @@
1
+ "use client";
2
+
3
+ import React, { HTMLInputTypeAttribute, useState } from "react";
4
+
5
+ import { ClassNamesFn } from "~utils/ClassNamesFn";
6
+
7
+ import styles from "./InputField.module.scss";
8
+ import { ClearIcon } from "~svg";
9
+
10
+ export type TOtpType = "OTP" | "TEXT";
11
+
12
+ interface IInputFieldProps {
13
+ onFocus?: () => void;
14
+ onBlur?: () => void;
15
+ onEnterPress?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
16
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
17
+ value?: string;
18
+ placeholder?: string;
19
+ className?: string;
20
+ style?: React.CSSProperties;
21
+ isClearable?: boolean;
22
+ inputLeftIcon?: JSX.Element;
23
+ type?: HTMLInputTypeAttribute;
24
+ id: string;
25
+ labelText?: string;
26
+ ariaLabel: string;
27
+ }
28
+
29
+ export const InputField = ({
30
+ onFocus,
31
+ onBlur,
32
+ onChange,
33
+ onEnterPress,
34
+ value = "",
35
+ inputLeftIcon,
36
+ placeholder = "",
37
+ className = "",
38
+ style,
39
+ isClearable = false,
40
+ type = "text",
41
+ id,
42
+ labelText = "",
43
+ ariaLabel = "",
44
+ }: IInputFieldProps): React.ReactNode => {
45
+ const [focused, setFocused] = useState<boolean>(false);
46
+ return (
47
+ <div
48
+ data-testid="InputField_MAIN"
49
+ className={ClassNamesFn(
50
+ styles[labelText.length ? "inputContainerLabeled" : "inputContainer"],
51
+ className,
52
+ focused ? styles[`input--onfocus`] : undefined,
53
+ styles[`input-${type?.toLocaleLowerCase()}`]
54
+ )}
55
+ style={style}
56
+ >
57
+ {labelText.length > 0 && (
58
+ <label htmlFor={id} data-testid="InputField_LABEL">
59
+ {labelText}
60
+ </label>
61
+ )}
62
+ {inputLeftIcon}
63
+ <input
64
+ data-testid="InputField_INPUT"
65
+ aria-label={ariaLabel}
66
+ id={id}
67
+ type={type}
68
+ className={styles.input}
69
+ placeholder={placeholder}
70
+ aria-placeholder={placeholder}
71
+ onFocus={() => {
72
+ setFocused(true);
73
+ if (onFocus) {
74
+ onFocus();
75
+ }
76
+ }}
77
+ onBlur={() => {
78
+ setFocused(false);
79
+ if (onBlur) {
80
+ onBlur();
81
+ }
82
+ }}
83
+ onChange={onChange}
84
+ onKeyDown={(event: React.KeyboardEvent<HTMLInputElement>) => {
85
+ if (onEnterPress && event.key === "Enter") {
86
+ onEnterPress(event);
87
+ }
88
+ }}
89
+ value={value}
90
+ readOnly={!onChange}
91
+ />
92
+ {isClearable && value && (
93
+ <ClearIcon
94
+ fill="red"
95
+ pathFill="#758393"
96
+ className={styles.clearIcon}
97
+ onClick={() => {
98
+ if (onChange) {
99
+ onChange({
100
+ target: { value: "" },
101
+ } as React.ChangeEvent<HTMLInputElement>);
102
+ }
103
+ }}
104
+ />
105
+ )}
106
+ </div>
107
+ );
108
+ };
@@ -1,3 +1,3 @@
1
- import { Button } from "./Button";
1
+ import { InputField } from "./InputField";
2
2
 
3
- export { Button };
3
+ export { InputField };
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+
3
+ export const ClearIcon = ({
4
+ width = 20,
5
+ height = 20,
6
+ fill = "none",
7
+ pathFill = "#fff",
8
+ }: React.SVGProps<SVGSVGElement> & { pathFill?: string }) => {
9
+ return (
10
+ <svg
11
+ data-testid="Icons_CLEAR"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ width={width}
14
+ height={height}
15
+ fill={fill}
16
+ viewBox="0 0 20 20"
17
+ >
18
+ <path
19
+ fill={pathFill}
20
+ fillRule="evenodd"
21
+ d="M10 18.333a8.333 8.333 0 100-16.666 8.333 8.333 0 000 16.666zM7.5 6.027L6.027 7.5l2.5 2.5-2.5 2.5L7.5 13.973l2.5-2.5 2.5 2.5 1.473-1.473-2.5-2.5 2.5-2.5L12.5 6.027l-2.5 2.5-2.5-2.5z"
22
+ clipRule="evenodd"
23
+ ></path>
24
+ </svg>
25
+ );
26
+ };
@@ -0,0 +1,3 @@
1
+ import { ClearIcon } from "./ClearIcon";
2
+
3
+ export { ClearIcon };
package/tsconfig.json CHANGED
@@ -23,7 +23,8 @@
23
23
  ],
24
24
  "paths": {
25
25
  "~utils/*": ["./src/utils/*"],
26
- "~baseComponents": ["./src/baseComponents/index"]
26
+ "~baseComponents": ["./src/baseComponents/index"],
27
+ "~svg": ["./src/svg/index"]
27
28
  }
28
29
  },
29
30
  "include": [
@@ -1,101 +0,0 @@
1
- @import '/src/styles/typography.module';
2
- @import '/src/styles/colors.module';
3
-
4
- .button {
5
- border: none;
6
- transition: background-color 0.2s ease;
7
-
8
- display: inline-flex;
9
- justify-content: center;
10
- align-items: center;
11
- cursor: pointer;
12
- }
13
-
14
- .btn-default {
15
- color: $default-white-color;
16
- background-color: $default-primary-accent;
17
- }
18
-
19
- .btn-default--disabled {
20
- color: $Text-Disabled-color;
21
- background-color: $button-disabled-default-color;
22
- }
23
-
24
- .btn-tinted {
25
- color: $Text-Accent;
26
- background-color: $button-tinted-default-color;
27
- }
28
-
29
- .btn-tinted--disabled {
30
- color: $Text-Disabled-Accent-color;
31
- background-color: $button-tinted-default-color;
32
- }
33
-
34
- .btn-secondary {
35
- color: $Text-Primary;
36
- background-color: $Surface-Surface-3-color;
37
- }
38
-
39
- .btn-secondary--disabled {
40
- color: $Text-Secondary;
41
- background-color: $Surface-Surface-3-color;
42
- }
43
-
44
- .btn-default:hover {
45
- background-color: $button-primary-on-hover-color;
46
- }
47
-
48
- .btn-tinted:hover {
49
- background-color: $button-tinted-on-hover-color;
50
- }
51
-
52
- .btn-secondary:hover {
53
- background-color: $button-secondary-on-hover;
54
- }
55
-
56
- .btn--mini {
57
- gap: 4px;
58
- padding: 6px 12px;
59
- @include caption2Medium;
60
- }
61
- .btn--small {
62
- gap: 8px;
63
- padding: 8px 16px;
64
- @include caption1Medium;
65
- }
66
- .btn--medium {
67
- gap: 8px;
68
- padding: 8px 20px;
69
- @include body2Medium;
70
- }
71
- .btn--large {
72
- gap: 8px;
73
- padding: 14px 24px;
74
- @include bodyMedium;
75
- }
76
-
77
- .btn-square--mini {
78
- border-radius: 4px;
79
- }
80
- .btn-square--small {
81
- border-radius: 6px;
82
- }
83
- .btn-square--medium {
84
- border-radius: 10px;
85
- }
86
- .btn-square--large {
87
- border-radius: 12px;
88
- }
89
-
90
- .btn-rounded--mini {
91
- border-radius: 32px;
92
- }
93
- .btn-rounded--small {
94
- border-radius: 32px;
95
- }
96
- .btn-rounded--medium {
97
- border-radius: 32px;
98
- }
99
- .btn-rounded--large {
100
- border-radius: 40px;
101
- }
@@ -1,49 +0,0 @@
1
- import React from 'react';
2
-
3
- import { ClassNamesFn } from '~utils/ClassNamesFn';
4
-
5
- import styles from './button.module.scss';
6
-
7
- interface IButtonProps {
8
- ariaLabel?: string;
9
- onClick?: () => void;
10
- children: React.ReactNode;
11
- className?: string;
12
- isRounded?: boolean;
13
- disabled?: boolean;
14
- variant?: 'default' | 'tinted' | 'secondary';
15
- size?: 'mini' | 'small' | 'medium' | 'large';
16
- style?: React.CSSProperties;
17
- }
18
-
19
- export const Button = ({
20
- onClick,
21
- children,
22
- style,
23
- className = '',
24
- isRounded = false,
25
- disabled = false,
26
- variant = 'default',
27
- size = 'medium',
28
- ariaLabel = 'Кнопка',
29
- }: IButtonProps) => (
30
- <button
31
- data-testid="Button_MAIN"
32
- aria-label={ariaLabel}
33
- disabled={disabled}
34
- aria-disabled={disabled}
35
- onClick={onClick}
36
- className={ClassNamesFn(
37
- styles[`btn--${size}`],
38
- isRounded
39
- ? styles[`btn-rounded--${size}`]
40
- : styles[`btn-square--${size}`],
41
- disabled ? styles[`btn-${variant}--disabled`] : styles[`btn-${variant}`],
42
- styles.button,
43
- className
44
- )}
45
- style={style}
46
- >
47
- {children}
48
- </button>
49
- );