@jk-core/components 0.0.1

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/.eslintrc.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true
5
+ },
6
+ "extends": [
7
+ "eslint:recommended",
8
+ "plugin:@typescript-eslint/recommended",
9
+ "plugin:react/recommended",
10
+ "airbnb",
11
+ "airbnb/hooks",
12
+ "airbnb-typescript"
13
+ ],
14
+ "parser": "@typescript-eslint/parser",
15
+ "parserOptions": {
16
+ "ecmaVersion": "latest",
17
+ "sourceType": "module",
18
+ "project": ["./tsconfig.json"]
19
+ },
20
+ "plugins": [
21
+ "@typescript-eslint",
22
+ "react"
23
+ ],
24
+ "rules": {
25
+ "import/prefer-default-export":"off",
26
+ "no-var": "error",
27
+ "jsx-a11y/control-has-associated-label":"off",
28
+ "jsx-a11y/label-has-associated-control": [
29
+ 2,
30
+ {
31
+ "labelAttributes": ["htmlFor"],
32
+ "depth": 3
33
+ }
34
+ ],
35
+ "react/react-in-jsx-scope": "off",
36
+ "react/jsx-props-no-spreading": "off",
37
+ "react/require-default-props": "off",
38
+ "react/jsx-one-expression-per-line": "off",
39
+ "implicit-arrow-linebreak": "off",
40
+ "linebreak-style" : "off",
41
+ "import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
42
+ "no-restricted-imports": [
43
+ "error",
44
+ {
45
+ "patterns": [
46
+ {
47
+ "group": ["../../../*"],
48
+ "message": "Please use absolute path."
49
+ }
50
+ ]
51
+ }
52
+ ],
53
+ "sort-imports": [
54
+ "error",
55
+ {
56
+ "ignoreCase": true,
57
+ "ignoreDeclarationSort": true,
58
+ "ignoreMemberSort": false,
59
+ "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
60
+ }
61
+ ]
62
+ }
63
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "extends": [
3
+ "stylelint-config-standard",
4
+ "stylelint-config-standard-scss"
5
+ ],
6
+ "plugins": ["stylelint-scss","stylelint-selector-bem-pattern"],
7
+ "recommendations": ["stylelint.vscode-stylelint"],
8
+ "rules": {
9
+ "property-no-vendor-prefix":null,
10
+ "import-notation": "string",
11
+ "selector-class-pattern": null,
12
+ "color-hex-length": null,
13
+ "no-descending-specificity": null,
14
+ "color-function-notation":null,
15
+ "alpha-value-notation":null,
16
+ "scss/percent-placeholder-pattern":null,
17
+ "custom-property-pattern":null,
18
+ "declaration-empty-line-before":null,
19
+ "scss/at-mixin-pattern":null,
20
+ "plugin/selector-bem-pattern": {
21
+ "componentName": "[A-Z]+",
22
+ "componentSelectors": {
23
+ "initial": "^\\.{componentName}(?:-[a-z]+)?$",
24
+ "combined": "^\\.combined-{componentName}-[a-z]+$"
25
+ },
26
+ "utilitySelectors": "^\\.util-[a-z]+$"
27
+ }
28
+ }
29
+ }
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@jk-core/components",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "license": "MIT",
7
+ "description": "components for jk",
8
+ "author": "KimKyungYun <kky38225221@gmail.com>",
9
+ "homepage": "https://bitbucket.org/jkcore/frontend_library#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://KyungYun@bitbucket.org/jkcore/frontend_library.git"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "bugs": {
18
+ "url": "https://bitbucket.org/jkcore/frontend_library/issues"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "require": "./dist/index.cjs"
25
+ }
26
+ },
27
+ "keywords": [
28
+ "utils",
29
+ "kyungyun",
30
+ "jk",
31
+ "components",
32
+ "calendar"
33
+ ],
34
+ "dependencies": {
35
+ "react": "^18.3.1",
36
+ "sass": "^1.79.4"
37
+ },
38
+ "scripts": {
39
+ "prepack": "yarn build",
40
+ "publish": "npm publish --access public",
41
+ "build": "yarn clean && vite build",
42
+ "clean": "rm -rf dist",
43
+ "lint": "yarn lint:eslint && yarn lint:stylelint",
44
+ "lint:eslint": "eslint ./src/ --ext .tsx,.ts",
45
+ "lint:stylelint": "stylelint \"./src/**/*.scss\" --config .stylelintrc.json"
46
+ },
47
+ "devDependencies": {
48
+ "@jk-core/utils": "workspace:*",
49
+ "@types/node": "^22.7.4",
50
+ "@types/react": "^18.3.11",
51
+ "@typescript-eslint/eslint-plugin": "^7.2.0",
52
+ "@typescript-eslint/parser": "^7.2.0",
53
+ "@vitejs/plugin-react-swc": "^3.7.1",
54
+ "eslint": "^8.57.0",
55
+ "eslint-config-airbnb": "^19.0.4",
56
+ "eslint-config-airbnb-typescript": "^18.0.0",
57
+ "eslint-import-resolver-typescript": "^3.6.1",
58
+ "eslint-plugin-import": "^2.29.1",
59
+ "eslint-plugin-jsx-a11y": "^6.8.0",
60
+ "eslint-plugin-react": "^7.34.2",
61
+ "eslint-plugin-react-hooks": "^4.6.2",
62
+ "eslint-plugin-react-refresh": "^0.4.6",
63
+ "optionator": "^0.9.4",
64
+ "postcss": "^8.4.38",
65
+ "stylelint": "^16.6.1",
66
+ "stylelint-config-standard": "^36.0.0",
67
+ "stylelint-config-standard-scss": "^13.1.0",
68
+ "stylelint-scss": "^6.5.1",
69
+ "stylelint-selector-bem-pattern": "^4.0.0",
70
+ "typescript": "^5.5.3",
71
+ "vite": "^5.4.8",
72
+ "vite-plugin-checker": "^0.8.0",
73
+ "vite-plugin-dts": "^4.2.3",
74
+ "vite-plugin-svgr": "^4.2.0",
75
+ "vite-tsconfig-paths": "^5.0.1"
76
+ }
77
+ }
@@ -0,0 +1,129 @@
1
+ .calendar {
2
+ width: 100%;
3
+ min-width: 300px;
4
+ border: 1px solid var(--G-30);
5
+ border-radius: 10px;
6
+ overflow: hidden;
7
+ color: var(--G-80);
8
+ background-color: var(--white);
9
+
10
+ &__close {
11
+ display: flex;
12
+ justify-content: flex-end;
13
+ align-items: center;
14
+ padding: 5px 5px 0 0;
15
+
16
+ svg {
17
+ width: 15px;
18
+ height: 15px;
19
+ cursor: pointer;
20
+ }
21
+ }
22
+ }
23
+
24
+ .view {
25
+ position: relative;
26
+ margin: 0 auto;
27
+ width: 90%;
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ background-color: #f3f4f8;
32
+ border-radius: 10px;
33
+
34
+ &__block {
35
+ position: absolute;
36
+ background-color: #fff;
37
+ left: 0;
38
+ height: 100%;
39
+ border: 2px solid var(--G-30);
40
+ width: 33.3%;
41
+ border-radius: 10px;
42
+ transition: 0.3s;
43
+
44
+ &--second {
45
+ left: 33%;
46
+ }
47
+
48
+ &--last {
49
+ left: 66.6%;
50
+ }
51
+ }
52
+
53
+ &__selector {
54
+ position: relative;
55
+ height: 40px;
56
+ flex: 1 0;
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ color: var(--G-60);
61
+ font-size: 1em;
62
+ font-weight: 400;
63
+
64
+ &--selected {
65
+ color: var(--G-80);
66
+ font-size: 1em;
67
+ font-weight: 600;
68
+ }
69
+ }
70
+ }
71
+
72
+ .nav {
73
+ height: 60px;
74
+ display: flex;
75
+ justify-content: space-between;
76
+ align-items: center;
77
+ padding: 0 5px;
78
+ border-bottom: 1px solid var(--G-30);
79
+ font-size: 1.3em;
80
+ font-weight: 400;
81
+
82
+ &__button {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ width: 40px;
87
+ height: 40px;
88
+ padding: 10px;
89
+ border-radius: 100%;
90
+
91
+ &:active {
92
+ background-color: var(--G-30);
93
+ }
94
+
95
+ &:disabled {
96
+ cursor: not-allowed;
97
+ fill: var(--G-40);
98
+ background-color: transparent;
99
+ }
100
+ }
101
+
102
+ &__label {
103
+ flex: 1 0;
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: space-around;
107
+ font-size: 1.1em;
108
+ font-weight: 400;
109
+
110
+ &--date {
111
+ display: flex;
112
+ align-items: center;
113
+ justify-content: center;
114
+ border-radius: 5px;
115
+ padding: 5px 10px;
116
+ font-size: 1.2em;
117
+ font-weight: 400;
118
+
119
+ svg {
120
+ width: 15px;
121
+ height: 15px;
122
+ }
123
+ }
124
+
125
+ &--date-selected {
126
+ background-color: var(--S-10);
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,79 @@
1
+ @use "../mixin.module.scss" as mixin;
2
+ @use "../../../styles/mediaQuery.scss" as media;
3
+
4
+ .body {
5
+ @include mixin.body;
6
+
7
+ &__tile {
8
+ display: flex;
9
+ flex-direction: column;
10
+ justify-content: space-between;
11
+ gap: 5px;
12
+ }
13
+
14
+ &__weeks {
15
+ display: flex;
16
+ justify-content: space-between;
17
+ font-weight: 400;
18
+ font-size: 1em;
19
+
20
+ &--date {
21
+ flex: 1 0;
22
+ display: flex;
23
+ justify-content: center;
24
+ align-items: center;
25
+ min-width: 40px;
26
+ padding: 5px 0;
27
+ }
28
+ }
29
+
30
+ &__week {
31
+ display: flex;
32
+ justify-content: space-between;
33
+ }
34
+
35
+ &__day {
36
+ display: flex;
37
+ justify-content: center;
38
+ align-items: center;
39
+ flex-direction: column;
40
+ width: 100%;
41
+ min-width: 40px;
42
+ min-height: 40px;
43
+ border-radius: 100%;
44
+ padding: 5px;
45
+ border: none;
46
+ font-weight: 400;
47
+ font-size: 1em;
48
+
49
+ &:hover {
50
+ @include media.pc {
51
+ background-color: var(--G-5);
52
+ }
53
+ }
54
+
55
+ &:active {
56
+ background-color: var(--G-10);
57
+ }
58
+
59
+ &--today {
60
+ color: var(--P-50);
61
+ font-weight: 600;
62
+ }
63
+
64
+ &--selected {
65
+ background-color: var(--P-50) !important;
66
+ color: var(--white) !important;
67
+ font-weight: 600;
68
+ }
69
+
70
+ &--before {
71
+ color: var(--G-40);
72
+ }
73
+
74
+ &--tile {
75
+ border-radius: 10px;
76
+ gap: 5px;
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,52 @@
1
+ /* eslint-disable max-len */
2
+ /* eslint-disable react/no-array-index-key */
3
+ import { cn } from '@jk-core/utils';
4
+ import isSameDay from '../../utils/isSameDay';
5
+ import styles from './DayTile.module.scss';
6
+
7
+ const WEEKS = ['일', '월', '화', '수', '목', '금', '토'];
8
+
9
+ interface Props {
10
+ selectedDate?: Date;
11
+ weeksInMonth: {
12
+ thisMonth: boolean;
13
+ date: Date;
14
+ }[][];
15
+ tileContent?: () => React.ReactNode;
16
+ handleDayClick: (day: Date) => void;
17
+ }
18
+ export default function DayTile({
19
+ selectedDate, weeksInMonth, tileContent, handleDayClick,
20
+ }: Props) {
21
+ return (
22
+ <div className={styles.body}>
23
+ <div className={styles.body__weeks}>
24
+ {WEEKS.map((week) => (<div className={styles['body__weeks--date']} key={week}>{week}</div>))}
25
+ </div>
26
+
27
+ <div className={styles.body__tile}>
28
+ {weeksInMonth.map((week, index) => (
29
+ <div key={index} className={styles.body__week}>
30
+ {week.map((day, idx) => (
31
+ <button
32
+ className={cn({
33
+ [styles.body__day]: true,
34
+ [styles['body__day--today']]: isSameDay(day.date, new Date()),
35
+ [styles['body__day--selected']]: !!selectedDate && isSameDay(day.date, selectedDate),
36
+ [styles['body__day--before']]: !day.thisMonth,
37
+ [styles['body__day--tile']]: !!(tileContent && tileContent()),
38
+ })}
39
+ type="button"
40
+ key={idx}
41
+ onClick={() => handleDayClick(day.date)}
42
+ >
43
+ {day.date.getDate()}
44
+ {tileContent && tileContent()}
45
+ </button>
46
+ ))}
47
+ </div>
48
+ ))}
49
+ </div>
50
+ </div>
51
+ );
52
+ }
@@ -0,0 +1,44 @@
1
+ @use "../mixin.module.scss" as mixin;
2
+
3
+ .body {
4
+ display: grid;
5
+ grid-template-columns: repeat(3, 1fr);
6
+ grid-template-rows: repeat(4, 1fr);
7
+ gap: 5px;
8
+
9
+ @include mixin.body;
10
+
11
+ &__month {
12
+ display: flex;
13
+ flex-direction: column;
14
+ justify-content: center;
15
+ align-items: center;
16
+ padding: 5px;
17
+ border-radius: 10px;
18
+
19
+ svg {
20
+ width: 15px;
21
+ height: 15px;
22
+ cursor: pointer;
23
+ }
24
+
25
+ &:active {
26
+ background-color: var(--G-10);
27
+ }
28
+
29
+ &--selected {
30
+ background-color: var(--P-50) !important;
31
+ color: var(--white) !important;
32
+ }
33
+
34
+ &--today {
35
+ color: var(--P-50);
36
+ font-weight: 600;
37
+ }
38
+
39
+ &--tile {
40
+ justify-content: flex-start;
41
+ gap: 5px;
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,38 @@
1
+ import { cn } from '@jk-core/utils';
2
+ import styles from './MonthTile.module.scss';
3
+ import isSameDay from '../../utils/isSameDay';
4
+ import { CalendarView } from '../../type';
5
+
6
+ const MONTHS = Array.from({ length: 12 }, (_, i) => i);
7
+
8
+ interface Props {
9
+ selectedDate?: Date;
10
+ viewDate: Date;
11
+ handleMonthClick: (month: number) => void;
12
+ tileContent?: (date: Date, view:CalendarView) => React.ReactNode;
13
+ }
14
+ export default function MonthTile({
15
+ selectedDate, viewDate, handleMonthClick, tileContent = () => false,
16
+ }: Props) {
17
+ return (
18
+ <div className={styles.body}>
19
+ {MONTHS.map((month) => (
20
+ <button
21
+ className={cn({
22
+ [styles.body__month]: true,
23
+ [styles['body__month--selected']]: !!selectedDate && isSameDay(selectedDate, new Date(viewDate.getFullYear(), month), 'month'),
24
+ [styles['body__month--today']]: isSameDay(new Date(viewDate.getFullYear(), month), new Date(), 'month'),
25
+ [styles['body__month--tile']]: !!tileContent(new Date(viewDate.getFullYear(), month - 1, 1), 'month'),
26
+ })}
27
+ type="button"
28
+ key={month}
29
+ onClick={() => handleMonthClick(month)}
30
+ >
31
+ <span>{`${month + 1}월`}</span>
32
+ {!!tileContent(new Date(viewDate.getFullYear(), month - 1, 1), 'month')
33
+ && tileContent(new Date(viewDate.getFullYear(), month - 1, 1), 'month')}
34
+ </button>
35
+ ))}
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,85 @@
1
+ @use "../mixin.module.scss" as mixin;
2
+ @use "../../../styles/mediaQuery.scss" as media;
3
+
4
+ .body {
5
+ @include mixin.body;
6
+
7
+ position: relative;
8
+ height: 310px;
9
+ display: flex;
10
+ flex-direction: column;
11
+ align-items: center;
12
+ overflow: auto;
13
+ gap: 10px;
14
+
15
+ &::-webkit-scrollbar {
16
+ display: none;
17
+ }
18
+
19
+ &__blank {
20
+ height: calc(50% - 40px);
21
+ flex-shrink: 0;
22
+
23
+ &:last-child {
24
+ height: 50%;
25
+ }
26
+ }
27
+
28
+ &__year {
29
+ min-width: 50%;
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ justify-content: center;
34
+ min-height: 40px;
35
+ border-radius: 6px;
36
+ flex-shrink: 0;
37
+ overflow: hidden;
38
+ font-weight: 400;
39
+ font-size: 1.2em;
40
+
41
+ &:hover {
42
+ @include media.pc {
43
+ background-color: var(--P-5);
44
+ }
45
+ }
46
+
47
+ &:active {
48
+ background-color: var(--P-10);
49
+ }
50
+
51
+ &--border {
52
+ border: 1px solid var(--G-30);
53
+ background-color: var(--P-5);
54
+
55
+ &:hover {
56
+ @include media.pc {
57
+ background-color: var(--P-10);
58
+ }
59
+ }
60
+ }
61
+
62
+ &--year {
63
+ height: 40px;
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ }
68
+
69
+ &--selected {
70
+ color: var(--white);
71
+ background-color: var(--P-50) !important;
72
+ }
73
+
74
+ &--tile {
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ min-height: 40px;
79
+ color: var(--G-80);
80
+ width: 100%;
81
+ background-color: var(--white);
82
+ border-top: var(--P-50);
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,60 @@
1
+ import { cn } from '@jk-core/utils';
2
+ import { useEffect, useRef } from 'react';
3
+ import styles from './YearTile.module.scss';
4
+ import { CalendarView } from '../../type';
5
+
6
+ interface Props {
7
+ selectedDate?: Date;
8
+ onClick: (year: number) => void;
9
+ tileContent?: (date: Date, view:CalendarView) => React.ReactNode;
10
+ }
11
+
12
+ const YEARS = Array.from({ length: 100 }, (_, i) => i + 2000);
13
+
14
+ export default function YearTile({
15
+ selectedDate, onClick,
16
+ tileContent = () => false,
17
+ }: Props) {
18
+ const wrapperRef = useRef<HTMLDivElement>(null);
19
+ const selectedRef = useRef<HTMLButtonElement>(null);
20
+
21
+ useEffect(() => {
22
+ const selectedElement = selectedRef.current;
23
+ const wrapperElement = wrapperRef.current;
24
+ if (!selectedElement || !wrapperElement) return;
25
+
26
+ const { clientHeight } = wrapperElement;
27
+ const { offsetTop, clientHeight: selectedHeight } = selectedElement;
28
+
29
+ wrapperElement.scrollTo({
30
+ top: offsetTop - clientHeight / 2 + selectedHeight,
31
+ });
32
+ }, []);
33
+
34
+ return (
35
+ <div className={styles.body} ref={wrapperRef}>
36
+ <div className={styles.body__blank} />
37
+ {YEARS.map((year) => (
38
+ <button
39
+ className={cn({
40
+ [styles.body__year]: true,
41
+ [styles['body__year--selected']]: !!selectedDate && selectedDate.getFullYear() === year,
42
+ [styles['body__year--border']]: !!tileContent(new Date(year, 1, 1), 'year'),
43
+ })}
44
+ key={year}
45
+ type="button"
46
+ ref={!!selectedDate && selectedDate.getFullYear() === year ? selectedRef : null}
47
+ onClick={() => onClick(year)}
48
+ >
49
+ <span className={styles['body__year--year']}>{year}</span>
50
+ {tileContent(new Date(year, 1, 1), 'year') && (
51
+ <div className={styles['body__year--tile']}>
52
+ {tileContent(new Date(year, 1, 1), 'year')}
53
+ </div>
54
+ )}
55
+ </button>
56
+ ))}
57
+ <div className={styles.body__blank} />
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,5 @@
1
+ @mixin body {
2
+ min-height: 310px;
3
+ padding: 5px;
4
+ background-color: var(--white);
5
+ }