diff --git a/package.json b/package.json index 48f5e2ae..ac627be0 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@fullcalendar/react": "^6.1.14", "@svgr/cli": "^8.1.0", + "fullcalendar": "^6.1.14", "react": "^18.3.1", "react-datepicker": "^7.2.0", "react-dom": "^18.3.1", diff --git a/src/components/common/button/RefreshBtn.tsx b/src/components/common/button/RefreshBtn.tsx index 592bf12d..09435823 100644 --- a/src/components/common/button/RefreshBtn.tsx +++ b/src/components/common/button/RefreshBtn.tsx @@ -30,6 +30,8 @@ const RefreshBtnCss = css` `; const RefreshBtnLayout = styled.button<{ isDisabled: boolean }>` + z-index: 2; + ${RefreshBtnCss} color: ${({ theme, isDisabled }) => (isDisabled ? theme.palette.Grey.Grey5 : theme.palette.Grey.White)}; diff --git a/src/components/common/fullCalendar/DayHeaderContent.tsx b/src/components/common/fullCalendar/DayHeaderContent.tsx new file mode 100644 index 00000000..9d0415e0 --- /dev/null +++ b/src/components/common/fullCalendar/DayHeaderContent.tsx @@ -0,0 +1,50 @@ +import styled from '@emotion/styled'; +import { DayHeaderContentArg } from '@fullcalendar/core'; + +interface DayHeaderContentProps { + arg: DayHeaderContentArg; + currentView: string; + today: string; +} + +function DayHeaderContent({ arg, currentView, today }: DayHeaderContentProps) { + const isTimeGridDay = currentView === 'timeGridDay'; + const day = new Intl.DateTimeFormat('en-US', { weekday: isTimeGridDay ? 'long' : 'short' }).format(arg.date); + const date = arg.date.getDate(); + const isToday = arg.date.toDateString() === today; + + return ( +
+ {!isTimeGridDay ? ( + <> + {day} + {currentView !== 'dayGridMonth' && {date}} + + ) : ( + + {date}일 {day} + + )} +
+ ); +} + +const DayLayout = styled.div` + display: flex; + gap: 1.2rem; + align-items: flex-end; + margin-left: 0.8rem; +`; + +const WeekDay = styled.div<{ isToday: boolean }>` + ${({ theme }) => theme.fontTheme.CAPTION_02}; + color: ${({ isToday, theme }) => (isToday ? theme.palette.Blue.Blue11 : theme.palette.Grey.Grey6)}; + text-transform: uppercase; +`; + +const WeekDate = styled.div<{ isToday: boolean }>` + ${({ theme }) => theme.fontTheme.HEADLINE_01}; + color: ${({ isToday, theme }) => (isToday ? theme.palette.Primary : theme.palette.Grey.Black)}; +`; + +export default DayHeaderContent; diff --git a/src/components/common/fullCalendar/FullCalendarBox.tsx b/src/components/common/fullCalendar/FullCalendarBox.tsx new file mode 100644 index 00000000..5a5ad3d8 --- /dev/null +++ b/src/components/common/fullCalendar/FullCalendarBox.tsx @@ -0,0 +1,108 @@ +import styled from '@emotion/styled'; +import { ViewMountArg, DatesSetArg } from '@fullcalendar/core'; +import dayGridPlugin from '@fullcalendar/daygrid'; +import interactionPlugin from '@fullcalendar/interaction'; +import FullCalendar from '@fullcalendar/react'; +import timeGridPlugin from '@fullcalendar/timegrid'; +import { useState } from 'react'; + +import RefreshBtn from '@/components/common/button/RefreshBtn'; +import DayHeaderContent from '@/components/common/fullCalendar/DayHeaderContent'; +import FullCalendarLayout from '@/components/common/fullCalendar/FullCalendarStyle'; +import { customDayCellContent, customSlotLabelContent } from '@/components/common/fullCalendar/fullCalendarUtils'; +import { theme } from '@/styles/theme'; + +interface FullCalendarBoxProps { + size: 'small' | 'big'; +} + +function FullCalendarBox({ size }: FullCalendarBoxProps) { + const today = new Date().toDateString(); + const [currentView, setCurrentView] = useState('timeGridWeek'); + + const handleViewChange = (view: ViewMountArg) => { + setCurrentView(view.view.type); + }; + + const handleDatesSet = (dateInfo: DatesSetArg) => { + setCurrentView(dateInfo.view.type); + }; + + return ( + + + + + } + viewDidMount={handleViewChange} + datesSet={handleDatesSet} + dayCellContent={customDayCellContent} + eventTimeFormat={{ + hour: 'numeric', + minute: '2-digit', + hour12: false, + }} + /> + + ); +} + +const CustomButtonContainer = styled.div` + display: flex; + align-items: center; + justify-content: flex-end; + box-sizing: border-box; + width: 100%; + margin-bottom: -2.6rem; + padding-right: 1rem; +`; + +export default FullCalendarBox; diff --git a/src/components/common/fullCalendar/FullCalendarStyle.ts b/src/components/common/fullCalendar/FullCalendarStyle.ts new file mode 100644 index 00000000..dbaef7f5 --- /dev/null +++ b/src/components/common/fullCalendar/FullCalendarStyle.ts @@ -0,0 +1,353 @@ +import styled from '@emotion/styled'; + +const FullCalendarLayout = styled.div<{ size: string }>` + width: ${({ size }) => (size === 'big' ? '89.7rem' : '58rem')}; + height: 93rem; + + .fc .fc-toolbar.fc-header-toolbar { + margin-bottom: 1.8rem; + } + + .fc .fc-timegrid-slot-label-cushion { + padding: 0 1.2rem 0 0; + } + + /* 이벤트 박스 */ + .fc-event-main { + display: flex; + align-items: center; + width: 100%; + height: 100%; + padding: 0.4rem 0.6rem; + + color: ${(color) => color.theme.palette.Grey.Grey8}; + + background-color: ${({ theme }) => theme.palette.Blue.Blue2}; + box-shadow: 2px 0 0 0 ${({ theme }) => theme.palette.Primary} inset; + border: none; + border-radius: 4px; + ${({ theme }) => theme.fontTheme.CAPTION_03}; + } + + .fc-v-event .fc-event-main-frame { + height: auto; + } + + .fc-daygrid-day-top { + height: 0; + } + + /* 요일 행 TEXT 중간 정렬 */ + .fc td, + .fc th { + vertical-align: middle; + ${({ theme }) => theme.fontTheme.CAPTION_01}; + } + + /* 타임 그리드 30분당 일정 */ + .fc .fc-timegrid-slot-label { + width: 5.7rem; + height: 2.4rem; + + color: ${(color) => color.theme.palette.Grey.Grey6}; + + border-bottom: none; + } + + /* 요일 행 첫번째 border 없애기 */ + .fc-theme-standard td:first-of-type, + .fc-theme-standard th:first-of-type { + border: none; + } + + /* 타임 그리드 종일 일정 */ + .fc-scrollgrid-shrink { + max-height: 2.4rem; + } + + /* 타임 그리드 종일 마진 없애기 */ + .fc .fc-daygrid-body-natural .fc-daygrid-day-events { + margin: 0; + + border-bottom: 1px solid ${({ theme }) => theme.palette.Grey.Grey9}; + } + + /* 30분 줄선 지우기 */ + .fc .fc-timegrid-slot-minor { + border-top-style: none; + } + + /* 요일 헤더 높이 조정 */ + + .fc .fc-col-header-cell { + box-sizing: border-box; + height: 5.5rem; + padding: 0.4rem 0.8rem 0.6rem; + + border-right: none; + border-left: none; + border-radius: 8px 8px 0 0; + } + + /* 주말 색 다르게 */ + .fc .fc-day-sun, + .fc .fc-day-sat { + background: ${({ theme }) => theme.palette.Blue.Blue1}; + } + + .fc .fc-button-primary:not(:disabled).fc-button-active { + background: ${({ theme }) => theme.palette.Primary}; + } + + .fc .fc-button-primary:focus { + box-shadow: none; + } + + /* Custom button styles */ + .fc-toolbar-chunk .fc-button { + width: 4.5rem; + height: 2.6rem; + padding: 0; + + background-color: ${({ theme }) => theme.palette.Blue.Blue3}; + border: none; + border-radius: 8px; + } + + .fc-toolbar-chunk .fc-button:active { + background-color: ${({ theme }) => theme.palette.Blue.Blue3}; + } + + /* Override the button group border-radius styles */ + .fc-direction-ltr .fc-button-group > .fc-button { + margin-right: 0.4rem; + margin-left: 0; + + border-radius: 8px; + } + + /* 스타일링 현재 시간 표시 */ + .fc .fc-timegrid-now-indicator-line { + height: 0.2rem; + + background-color: ${({ theme }) => theme.palette.Primary}; + border: none; + } + + /* 시간 세로줄 테두리 없애기 */ + .fc-timegrid-axis { + color: ${({ theme }) => theme.palette.Grey.Grey6}; + + border: none; + } + + /* 오늘 배경색 없애기 */ + .fc .fc-day-today { + background: none; + } + + /* event에 있는 기본 스타일 지우기 */ + .fc-timegrid-event-harness-inset .fc-timegrid-event { + box-shadow: none; + border: none; + } + + /* event inset 적용 */ + .fc-timegrid-event-harness > .fc-timegrid-event { + inset: 0.1rem; + } + + /* 좌우 버튼 스타일 */ + .fc-toolbar-chunk .fc-prev-button, + .fc-toolbar-chunk .fc-next-button { + width: 2.6rem; + height: 2.6rem; + padding: 0; + + background-color: ${({ theme }) => theme.palette.Grey.Black}; + } + + .fc-toolbar-chunk .fc-button:hover { + background-color: ${({ theme }) => theme.palette.Blue.Blue8}; + } + + .fc-toolbar-chunk .fc-prev-button:hover, + .fc-toolbar-chunk .fc-next-button:hover { + background-color: ${({ theme }) => theme.palette.Grey.Grey7}; + } + + .fc-direction-ltr .fc-toolbar > * > :not(:first-of-type) { + margin-left: 0.4rem; + } + + .fc-button-active:focus { + box-shadow: none; + } + + .fc-toolbar-chunk { + display: flex; + align-items: center; + } + + /* 오늘 버튼 */ + .fc-toolbar-chunk .fc-today-button { + background-color: ${({ theme }) => theme.palette.Grey.Black}; + opacity: 1; + } + + .fc-toolbar-chunk .fc-today-button:hover { + background-color: ${({ theme }) => theme.palette.Grey.Grey7}; + } + + .fc-toolbar-chunk .fc-today-button:active { + background-color: ${({ theme }) => theme.palette.Grey.Grey8}; + } + + .fc .fc-button-group { + margin-left: 5.4rem; + } + + .fc .fc-custom-button { + background-color: ${({ theme }) => theme.palette.Grey.Black}; + } + + .fc .fc-custom-button:hover { + background-color: ${({ theme }) => theme.palette.Grey.Grey7}; + } + + /* 오늘 버튼 마진 */ + .fc .fc-toolbar-title { + margin-right: 0.75rem; + padding: 0 1rem; + ${({ theme }) => theme.fontTheme.HEADLINE_02}; + } + + /* 종일 이벤트 테두리 */ + .fc .fc-daygrid-day-frame .fc-event-main { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + height: 2.1rem; + padding: 0.3rem 1.2rem; + + color: ${({ theme }) => theme.palette.Grey.White}; + + background-color: ${({ theme }) => theme.palette.Primary}; + border: none; + } + + .fc .fc-daygrid-event-harness { + margin: 0; + } + + .fc .fc-daygrid-day-frame .fc-daygrid-event-harness { + background-color: ${({ theme }) => theme.palette.Grey.White}; + } + + .fc .fc-daygrid-event { + margin: 0; + } + + .fc .fc-cell-shaded { + display: none; + } + + /* 현재시간 화살표 지우기 */ + .fc-timegrid-now-indicator-arrow { + display: none; + } + + .fc-direction-ltr .fc-daygrid-event { + display: flex; + justify-content: center; + box-sizing: border-box; + height: 2.1rem; + margin: 0.1rem; + + background-color: ${({ theme }) => theme.palette.Primary}; + } + + .fc .fc-daygrid-dot-event { + padding: 0.4rem 0.6rem; + + background-color: ${({ theme }) => theme.palette.Blue.Blue2}; + border-left: 2px solid ${({ theme }) => theme.palette.Primary}; + border-radius: 4px; + } + + .fc .fc-h-event { + border: none; + } + + .fc-daygrid-event-dot { + display: none; + } + + .fc .fc-timegrid-axis-frame { + justify-content: flex-start; + } + + /* 시간 왼쪽에 붙이기 */ + .fc-direction-ltr .fc-timegrid-slot-label-frame { + text-align: left; + } + + /* 이벤트 꽉차게 */ + .fc-direction-ltr .fc-timegrid-col-events { + margin: 0; + } + + /* 버튼 focus 그림자 없애기 */ + .fc .fc-button-primary:not(:disabled).fc-button-active:focus, + .fc .fc-button-primary:not(:disabled):active:focus { + box-shadow: none; + } + + /* 바깥 테두리 없애기 */ + .fc .fc-scrollgrid-liquid { + height: 65.5rem; + overflow: auto; + + border: none; + + scrollbar-width: thin; + scrollbar-color: ${({ theme }) => theme.palette.Grey.Grey6} transparent; + } + + /* 스크롤 커스텀 */ + .fc-scrollgrid-liquid::-webkit-scrollbar { + width: 0.6rem; + } + + .fc-scrollgrid-liquid::-webkit-scrollbar-thumb { + width: 0.6rem; + + background-color: ${({ theme }) => theme.palette.Grey.Grey6}; + border-radius: 3px; + } + + /* stylelint-disable selector-class-pattern */ + + /* 일간에는 주말표시 안하기 */ + .fc .fc-timeGridDay-view .fc-day-sun, + .fc .fc-timeGridDay-view .fc-day-sat { + background: none; + } + + .fc-dayGridMonth-view .fc-day-sun .fc-daygrid-day-frame { + box-shadow: 0 1px 0 0 ${({ theme }) => theme.palette.Grey.Grey9} inset; + } + + .fc .fc-dayGridMonth-view .fc-scrollgrid-section-body table { + border: 1px solid ${({ theme }) => theme.palette.Grey.Grey9}; + } + + .fc .fc-timeGridDay-view .fc-col-header-cell-cushion { + float: left; + } + + /* stylelint-enable selector-class-pattern */ +`; + +export default FullCalendarLayout; diff --git a/src/components/common/fullCalendar/fullCalendarUtils.ts b/src/components/common/fullCalendar/fullCalendarUtils.ts new file mode 100644 index 00000000..2f516c04 --- /dev/null +++ b/src/components/common/fullCalendar/fullCalendarUtils.ts @@ -0,0 +1,33 @@ +import { DayCellContentArg, SlotLabelContentArg } from '@fullcalendar/core'; + +// 월간 달력에서 날짜 '일' 제거 +export const customDayCellContent = (info: DayCellContentArg) => { + const number = document.createElement('a'); + number.classList.add('fc-daygrid-day-number'); + number.innerHTML = info.dayNumberText.replace('일', ''); + + if (info.view.type === 'dayGridMonth') { + return { + html: number.outerHTML, + }; + } + + return { + domNodes: [], + }; +}; + +// 일간, 주간에서 왼쪽 시간형식 '12 AM' 으로 만들기 +export const customSlotLabelContent = (arg: SlotLabelContentArg) => { + const formattedTime = new Intl.DateTimeFormat('en-US', { + hour: 'numeric', + hour12: true, + }).format(arg.date); + + const span = document.createElement('span'); + span.innerText = formattedTime; + + return { + html: span.outerHTML, + }; +}; diff --git a/src/pages/Calendar.tsx b/src/pages/Calendar.tsx index 2c8bc553..c6758831 100644 --- a/src/pages/Calendar.tsx +++ b/src/pages/Calendar.tsx @@ -1,5 +1,10 @@ +import FullCalendarBox from '@/components/common/fullCalendar/FullCalendarBox'; + function Calendar() { - return
calendar
; + return ( +
+ +
+ ); } - export default Calendar; diff --git a/src/styles/GlobalStyle.tsx b/src/styles/GlobalStyle.tsx index 00fedbcb..29c43d97 100644 --- a/src/styles/GlobalStyle.tsx +++ b/src/styles/GlobalStyle.tsx @@ -147,6 +147,10 @@ const style = css` html { font-size: 62.5%; } + + :root { + --fc-highlight-color: #dfe9ff; + } `; function GlobalStyle() { diff --git a/src/styles/palette.ts b/src/styles/palette.ts index 1f4bf7c8..f1e38f77 100644 --- a/src/styles/palette.ts +++ b/src/styles/palette.ts @@ -12,6 +12,7 @@ const palette = { Blue8: '#245CCE', Blue9: '#0D47A1', Blue10: '#173B86', + Blue11: '#B9D0FF', }, Orange: { Orange1: '#FFE7DF', @@ -34,6 +35,7 @@ const palette = { Grey6: '#626273', Grey7: '#464656', Grey8: '#34343C', + Grey9: '#E0E0E0', Black: '#212121', }, }; diff --git a/yarn.lock b/yarn.lock index f6c221eb..d4cf2b9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1474,11 +1474,13 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + "@fal-works/esbuild-plugin-global-externals@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4" integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ== + "@floating-ui/core@^1.6.0": version "1.6.4" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.4.tgz#0140cf5091c8dee602bff9da5ab330840ff91df6" @@ -1515,6 +1517,47 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.4.tgz#1d459cee5031893a08a0e064c406ad2130cced7c" integrity sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA== +"@fullcalendar/core@~6.1.14": + version "6.1.14" + resolved "https://registry.yarnpkg.com/@fullcalendar/core/-/core-6.1.14.tgz#75cd70ab28600dec630970e1015e4e7b59cd4a94" + integrity sha512-hIPRBevm0aMc2aHy1hRIJgXmI1QTvQM1neQa9oxtuqUmF1+ApYC3oAdwcQMTuI7lHHw3pKJDyJFkKLPPnL6HXA== + dependencies: + preact "~10.12.1" + +"@fullcalendar/daygrid@~6.1.14": + version "6.1.14" + resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-6.1.14.tgz#4afc48adbdd905b79777ff4c822c9ed0205df995" + integrity sha512-DSyjiA1dEM8k3bOCrZpZOmAOZu71KGtH02ze+4QKuhxkmn/zQghmmLRdfzpOrcyJg6xGKkoB4pBcO+2lXar8XQ== + +"@fullcalendar/interaction@~6.1.14": + version "6.1.14" + resolved "https://registry.yarnpkg.com/@fullcalendar/interaction/-/interaction-6.1.14.tgz#c758581419e324cf82b53a38e7941942f7f0dd14" + integrity sha512-rXum5XCjq+WEPNctFeYL/JKZGeU2rlxrElygocdMegcrIBJQW5hnWWVE+i4/1dOmUKF80CbGVlXUyYXoqK2eFg== + +"@fullcalendar/list@~6.1.14": + version "6.1.14" + resolved "https://registry.yarnpkg.com/@fullcalendar/list/-/list-6.1.14.tgz#526a8f9ce1d98457ab030c3e4ae47302afb92ebf" + integrity sha512-eV0/6iCumYfvlPzIUTAONWH17/JlQCyCChUz8m06L4E/sOiNjkHGz8vlVTmZKqXzx9oWOOyV/Nm3pCtHmVZh+Q== + +"@fullcalendar/multimonth@~6.1.14": + version "6.1.14" + resolved "https://registry.yarnpkg.com/@fullcalendar/multimonth/-/multimonth-6.1.14.tgz#df82f13a5c9f44d6f6bd0e26fa306dd6be33f926" + integrity sha512-el2vbZZgTkdufgOvRxqx61czjRMfEK50449g4SkqbagtS3ITNMAv84KHFcsbXVbd9Nh3UhbXDuYZuzJZpvY7mQ== + dependencies: + "@fullcalendar/daygrid" "~6.1.14" + +"@fullcalendar/react@^6.1.14": + version "6.1.14" + resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-6.1.14.tgz#18adcb690074e933aeae6b26474f65bd22198021" + integrity sha512-sXLn2D8aPYLuDH3fy2ZhHTOz5WNSU1NhoECsGBzjUtz2IYHy6m5Y9TqlyqeAqVqFLDRSJAlKAr5LyrIvnD/IMA== + +"@fullcalendar/timegrid@~6.1.14": + version "6.1.14" + resolved "https://registry.yarnpkg.com/@fullcalendar/timegrid/-/timegrid-6.1.14.tgz#ff1849278b4991002ae1337d5435aaba3ae9aadc" + integrity sha512-ZByc3BVAtxWSVfyaNedROLlg/Tb2NQ43+MZZAfBSrVwVm2xyfQ+Bsx3pJyCXsRsUh2TFFTO07q7nMWe0jet3KQ== + dependencies: + "@fullcalendar/daygrid" "~6.1.14" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -5157,6 +5200,18 @@ fsevents@~2.3.2, fsevents@~2.3.3: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +fullcalendar@^6.1.14: + version "6.1.14" + resolved "https://registry.yarnpkg.com/fullcalendar/-/fullcalendar-6.1.14.tgz#64858d63afc341793fe363411798b0b9663cd968" + integrity sha512-uGaJrhafNE50VwHRkIROxXMKDZUr7yXgCJ+8VZ561oL5kx2sSWaDsa9WrSYH/NedNJIklUP5XczhIVtcT3aiaQ== + dependencies: + "@fullcalendar/core" "~6.1.14" + "@fullcalendar/daygrid" "~6.1.14" + "@fullcalendar/interaction" "~6.1.14" + "@fullcalendar/list" "~6.1.14" + "@fullcalendar/multimonth" "~6.1.14" + "@fullcalendar/timegrid" "~6.1.14" + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -6877,6 +6932,11 @@ postcss@^8.4.21, postcss@^8.4.32, postcss@^8.4.38, postcss@^8.4.39: picocolors "^1.0.1" source-map-js "^1.2.0" +preact@~10.12.1: + version "10.12.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.12.1.tgz#8f9cb5442f560e532729b7d23d42fd1161354a21" + integrity sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"