@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 +63 -0
- package/.stylelintrc.json +29 -0
- package/package.json +77 -0
- package/src/Calendar/Calendar.module.scss +129 -0
- package/src/Calendar/components/DayTile/DayTile.module.scss +79 -0
- package/src/Calendar/components/DayTile/index.tsx +52 -0
- package/src/Calendar/components/MonthTile/MonthTile.module.scss +44 -0
- package/src/Calendar/components/MonthTile/index.tsx +38 -0
- package/src/Calendar/components/YearTile/YearTile.module.scss +85 -0
- package/src/Calendar/components/YearTile/index.tsx +60 -0
- package/src/Calendar/components/mixin.module.scss +5 -0
- package/src/Calendar/hooks/useCalendarNav.ts +80 -0
- package/src/Calendar/hooks/useDateSelect.ts +47 -0
- package/src/Calendar/index.tsx +166 -0
- package/src/Calendar/type.ts +6 -0
- package/src/Calendar/utils/getWeeksInMonth.ts +45 -0
- package/src/Calendar/utils/isInRange.ts +8 -0
- package/src/Calendar/utils/isSameDay.ts +21 -0
- package/src/assets/close.svg +16 -0
- package/src/assets/drop-arrow.svg +3 -0
- package/src/index.tsx +3 -0
- package/src/styles/color.scss +90 -0
- package/src/styles/mediaQuery.scss +22 -0
- package/src/svg.d.ts +6 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +13 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +52 -0
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
|
+
}
|