@conscience-technology/ctds 0.0.0-stage → 0.1.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.
- package/README.md +128 -2
- package/assets/components/AppFrame/AppFrame.module.css +177 -0
- package/assets/components/AppFrame/AppFrame.tsx +93 -0
- package/assets/components/AppFrame/index.ts +6 -0
- package/assets/components/AttachmentChip/AttachmentChip.module.css +71 -0
- package/assets/components/AttachmentChip/AttachmentChip.tsx +74 -0
- package/assets/components/AttachmentChip/index.ts +2 -0
- package/assets/components/AttachmentThumb/AttachmentThumb.module.css +107 -0
- package/assets/components/AttachmentThumb/AttachmentThumb.tsx +91 -0
- package/assets/components/AttachmentThumb/index.ts +2 -0
- package/assets/components/Avatar/Avatar.module.css +167 -0
- package/assets/components/Avatar/Avatar.tsx +228 -0
- package/assets/components/Avatar/index.ts +2 -0
- package/assets/components/AvatarUpload/AvatarUpload.module.css +130 -0
- package/assets/components/AvatarUpload/AvatarUpload.tsx +139 -0
- package/assets/components/AvatarUpload/index.ts +2 -0
- package/assets/components/Badge/Badge.module.css +76 -0
- package/assets/components/Badge/Badge.tsx +68 -0
- package/assets/components/Badge/index.ts +2 -0
- package/assets/components/Breadcrumb/Breadcrumb.module.css +213 -0
- package/assets/components/Breadcrumb/Breadcrumb.tsx +279 -0
- package/assets/components/Breadcrumb/index.ts +8 -0
- package/assets/components/Button/Button.module.css +216 -0
- package/assets/components/Button/Button.tsx +149 -0
- package/assets/components/Button/index.ts +2 -0
- package/assets/components/Callout/Callout.module.css +146 -0
- package/assets/components/Callout/Callout.tsx +138 -0
- package/assets/components/Callout/index.ts +2 -0
- package/assets/components/ChatInput/ChatInput.module.css +241 -0
- package/assets/components/ChatInput/ChatInput.tsx +322 -0
- package/assets/components/ChatInput/ModelSelector.tsx +40 -0
- package/assets/components/ChatInput/SendButton.tsx +41 -0
- package/assets/components/ChatInput/index.ts +10 -0
- package/assets/components/Checkbox/Checkbox.module.css +66 -0
- package/assets/components/Checkbox/Checkbox.tsx +36 -0
- package/assets/components/Checkbox/index.ts +2 -0
- package/assets/components/Chip/Chip.module.css +128 -0
- package/assets/components/Chip/Chip.tsx +103 -0
- package/assets/components/Chip/index.ts +2 -0
- package/assets/components/ChoiceCard/ChoiceCard.module.css +142 -0
- package/assets/components/ChoiceCard/ChoiceCard.tsx +132 -0
- package/assets/components/ChoiceCard/index.ts +2 -0
- package/assets/components/ContextMenu/ContextMenu.module.css +102 -0
- package/assets/components/ContextMenu/ContextMenu.tsx +392 -0
- package/assets/components/ContextMenu/index.ts +9 -0
- package/assets/components/DatePicker/Calendar.tsx +234 -0
- package/assets/components/DatePicker/DESIGN_NOTES.md +106 -0
- package/assets/components/DatePicker/DESIGN_NOTES_TIME.md +125 -0
- package/assets/components/DatePicker/DatePicker.module.css +295 -0
- package/assets/components/DatePicker/DatePicker.tsx +387 -0
- package/assets/components/DatePicker/TimeSelector.tsx +69 -0
- package/assets/components/DatePicker/index.ts +8 -0
- package/assets/components/DatePicker/presets.ts +80 -0
- package/assets/components/DatePicker/utils.ts +148 -0
- package/assets/components/Disclosure/Disclosure.module.css +73 -0
- package/assets/components/Disclosure/Disclosure.tsx +129 -0
- package/assets/components/Disclosure/index.ts +6 -0
- package/assets/components/Drawer/Drawer.module.css +257 -0
- package/assets/components/Drawer/Drawer.tsx +126 -0
- package/assets/components/Drawer/index.ts +7 -0
- package/assets/components/EmptyState/EmptyState.module.css +102 -0
- package/assets/components/EmptyState/EmptyState.tsx +80 -0
- package/assets/components/EmptyState/index.ts +2 -0
- package/assets/components/FileIcon/FileIcon.module.css +66 -0
- package/assets/components/FileIcon/FileIcon.tsx +84 -0
- package/assets/components/FileIcon/fileExtMap.ts +112 -0
- package/assets/components/FileIcon/index.ts +9 -0
- package/assets/components/FilterButton/FilterButton.module.css +130 -0
- package/assets/components/FilterButton/FilterButton.tsx +94 -0
- package/assets/components/FilterButton/index.ts +2 -0
- package/assets/components/GalleryItem/GalleryItem.module.css +220 -0
- package/assets/components/GalleryItem/GalleryItem.tsx +163 -0
- package/assets/components/GalleryItem/index.ts +7 -0
- package/assets/components/Icon/Icon.tsx +616 -0
- package/assets/components/Icon/context.tsx +39 -0
- package/assets/components/Icon/index.ts +4 -0
- package/assets/components/IconButton/IconButton.module.css +109 -0
- package/assets/components/IconButton/IconButton.tsx +110 -0
- package/assets/components/IconButton/index.ts +2 -0
- package/assets/components/Input/Input.module.css +338 -0
- package/assets/components/Input/Input.tsx +371 -0
- package/assets/components/Input/index.ts +2 -0
- package/assets/components/ListItem/ListItem.module.css +318 -0
- package/assets/components/ListItem/ListItem.tsx +262 -0
- package/assets/components/ListItem/index.ts +9 -0
- package/assets/components/MenuItem/MenuItem.module.css +437 -0
- package/assets/components/MenuItem/MenuItem.tsx +529 -0
- package/assets/components/MenuItem/index.ts +8 -0
- package/assets/components/MetricCard/MetricCard.module.css +153 -0
- package/assets/components/MetricCard/MetricCard.tsx +140 -0
- package/assets/components/MetricCard/index.ts +2 -0
- package/assets/components/Modal/Modal.module.css +175 -0
- package/assets/components/Modal/Modal.tsx +98 -0
- package/assets/components/Modal/index.ts +2 -0
- package/assets/components/Onboarding/DocsIndex.tsx +82 -0
- package/assets/components/Onboarding/LayoutBuilder.tsx +160 -0
- package/assets/components/Onboarding/Onboarding.module.css +1394 -0
- package/assets/components/Onboarding/OnboardingAxisPanel.tsx +515 -0
- package/assets/components/Onboarding/OnboardingPage.tsx +1193 -0
- package/assets/components/Onboarding/PresetsGallery.tsx +65 -0
- package/assets/components/Onboarding/actions.ts +250 -0
- package/assets/components/Onboarding/index.ts +8 -0
- package/assets/components/Onboarding/palette.css +40 -0
- package/assets/components/Onboarding/wizardData.ts +265 -0
- package/assets/components/Pagination/Pagination.module.css +82 -0
- package/assets/components/Pagination/Pagination.tsx +248 -0
- package/assets/components/Pagination/index.ts +6 -0
- package/assets/components/ProgressBar/ProgressBar.module.css +64 -0
- package/assets/components/ProgressBar/ProgressBar.tsx +54 -0
- package/assets/components/ProgressBar/index.ts +2 -0
- package/assets/components/QuotationBar/QuotationBar.module.css +61 -0
- package/assets/components/QuotationBar/QuotationBar.tsx +48 -0
- package/assets/components/QuotationBar/index.ts +2 -0
- package/assets/components/Radio/Radio.module.css +63 -0
- package/assets/components/Radio/Radio.tsx +33 -0
- package/assets/components/Radio/index.ts +2 -0
- package/assets/components/Select/Select.module.css +151 -0
- package/assets/components/Select/Select.tsx +425 -0
- package/assets/components/Select/index.ts +10 -0
- package/assets/components/SelectableCard/SelectableCard.module.css +177 -0
- package/assets/components/SelectableCard/SelectableCard.tsx +83 -0
- package/assets/components/SelectableCard/index.ts +2 -0
- package/assets/components/Shortcut/Shortcut.module.css +56 -0
- package/assets/components/Shortcut/Shortcut.tsx +156 -0
- package/assets/components/Shortcut/index.ts +2 -0
- package/assets/components/Sidebar/Sidebar.module.css +242 -0
- package/assets/components/Sidebar/Sidebar.tsx +264 -0
- package/assets/components/Sidebar/index.ts +7 -0
- package/assets/components/SidebarSplit/SidebarSplit.module.css +126 -0
- package/assets/components/SidebarSplit/SidebarSplit.tsx +222 -0
- package/assets/components/SidebarSplit/index.ts +6 -0
- package/assets/components/Slider/Slider.module.css +120 -0
- package/assets/components/Slider/Slider.tsx +88 -0
- package/assets/components/Slider/index.ts +2 -0
- package/assets/components/SplitButton/SplitButton.module.css +190 -0
- package/assets/components/SplitButton/SplitButton.tsx +145 -0
- package/assets/components/SplitButton/index.ts +6 -0
- package/assets/components/Stepper/Stepper.module.css +182 -0
- package/assets/components/Stepper/Stepper.tsx +185 -0
- package/assets/components/Stepper/index.ts +2 -0
- package/assets/components/Switch/Switch.module.css +116 -0
- package/assets/components/Switch/Switch.tsx +58 -0
- package/assets/components/Switch/index.ts +2 -0
- package/assets/components/TableCell/TableCell.module.css +137 -0
- package/assets/components/TableCell/TableCellAvatar.tsx +42 -0
- package/assets/components/TableCell/TableCellBadge.tsx +23 -0
- package/assets/components/TableCell/TableCellIcon.tsx +39 -0
- package/assets/components/TableCell/TableCellImage.tsx +49 -0
- package/assets/components/TableCell/TableCellLink.tsx +33 -0
- package/assets/components/TableCell/TableCellMeter.tsx +55 -0
- package/assets/components/TableCell/TableCellNumber.tsx +43 -0
- package/assets/components/TableCell/TableCellText.tsx +30 -0
- package/assets/components/TableCell/index.ts +23 -0
- package/assets/components/TableHeader/TableHeader.module.css +287 -0
- package/assets/components/TableHeader/TableHeader.tsx +590 -0
- package/assets/components/TableHeader/index.ts +9 -0
- package/assets/components/TableRow/TableRow.module.css +290 -0
- package/assets/components/TableRow/TableRow.tsx +253 -0
- package/assets/components/TableRow/index.ts +8 -0
- package/assets/components/TableRowEdit/TableRowEdit.module.css +126 -0
- package/assets/components/TableRowEdit/TableRowEdit.tsx +247 -0
- package/assets/components/TableRowEdit/index.ts +2 -0
- package/assets/components/TableRowSelect/TableRowSelect.module.css +23 -0
- package/assets/components/TableRowSelect/TableRowSelect.tsx +175 -0
- package/assets/components/TableRowSelect/index.ts +2 -0
- package/assets/components/Tabs/Tabs.module.css +234 -0
- package/assets/components/Tabs/Tabs.tsx +426 -0
- package/assets/components/Tabs/index.ts +9 -0
- package/assets/components/Textarea/Textarea.module.css +318 -0
- package/assets/components/Textarea/Textarea.tsx +426 -0
- package/assets/components/Textarea/index.ts +7 -0
- package/assets/components/ThemeController.tsx +271 -0
- package/assets/components/TimePicker/TimePicker.module.css +317 -0
- package/assets/components/TimePicker/TimePicker.tsx +821 -0
- package/assets/components/TimePicker/index.ts +8 -0
- package/assets/components/TimePicker/utils.ts +115 -0
- package/assets/components/Toast/Toast.module.css +264 -0
- package/assets/components/Toast/Toast.tsx +243 -0
- package/assets/components/Toast/index.ts +15 -0
- package/assets/components/Toolbar/Toolbar.module.css +72 -0
- package/assets/components/Toolbar/Toolbar.tsx +75 -0
- package/assets/components/Toolbar/index.ts +2 -0
- package/assets/components/Tooltip/Tooltip.module.css +42 -0
- package/assets/components/Tooltip/Tooltip.tsx +332 -0
- package/assets/components/Tooltip/index.ts +2 -0
- package/assets/components/Topbar/Topbar.module.css +26 -0
- package/assets/components/Topbar/Topbar.tsx +47 -0
- package/assets/components/Topbar/index.ts +2 -0
- package/assets/components/_cn.ts +3 -0
- package/assets/components/_color.ts +48 -0
- package/assets/components/_data.ts +659 -0
- package/assets/components/_utils.css +19 -0
- package/assets/components/axisOptions.ts +24 -0
- package/assets/components/tokens.css +742 -0
- package/assets/prompts/GUIDE.md +251 -0
- package/assets/prompts/OVERRIDE.md +170 -0
- package/assets/prompts/PRINCIPLES.md +130 -0
- package/assets/prompts/axes/density.md +38 -0
- package/assets/prompts/axes/font.md +36 -0
- package/assets/prompts/axes/hue.md +36 -0
- package/assets/prompts/axes/icon.md +48 -0
- package/assets/prompts/axes/mode.md +50 -0
- package/assets/prompts/axes/motion.md +77 -0
- package/assets/prompts/axes/palette.md +35 -0
- package/assets/prompts/axes/shape.md +33 -0
- package/assets/prompts/axes/voice.md +54 -0
- package/assets/prompts/components/README.md +84 -0
- package/assets/prompts/components/app-frame.md +94 -0
- package/assets/prompts/components/attachment-chip.md +46 -0
- package/assets/prompts/components/attachment-thumb.md +49 -0
- package/assets/prompts/components/avatar.md +86 -0
- package/assets/prompts/components/badge.md +104 -0
- package/assets/prompts/components/breadcrumb.md +77 -0
- package/assets/prompts/components/button.md +99 -0
- package/assets/prompts/components/callout.md +81 -0
- package/assets/prompts/components/chat-input.md +84 -0
- package/assets/prompts/components/checkbox.md +46 -0
- package/assets/prompts/components/chip.md +84 -0
- package/assets/prompts/components/choice-card.md +127 -0
- package/assets/prompts/components/context-menu.md +145 -0
- package/assets/prompts/components/date-picker.md +104 -0
- package/assets/prompts/components/drawer.md +152 -0
- package/assets/prompts/components/empty-state.md +69 -0
- package/assets/prompts/components/file-icon.md +73 -0
- package/assets/prompts/components/filter-button.md +42 -0
- package/assets/prompts/components/form-field.md +74 -0
- package/assets/prompts/components/gallery-item.md +158 -0
- package/assets/prompts/components/icon-button.md +82 -0
- package/assets/prompts/components/icon.md +68 -0
- package/assets/prompts/components/input.md +95 -0
- package/assets/prompts/components/list-item.md +235 -0
- package/assets/prompts/components/menu-item.md +234 -0
- package/assets/prompts/components/metric-card.md +132 -0
- package/assets/prompts/components/modal.md +131 -0
- package/assets/prompts/components/pagination.md +92 -0
- package/assets/prompts/components/progress-bar.md +61 -0
- package/assets/prompts/components/quotation-bar.md +39 -0
- package/assets/prompts/components/radio.md +41 -0
- package/assets/prompts/components/select.md +104 -0
- package/assets/prompts/components/selectable-card.md +67 -0
- package/assets/prompts/components/shortcut.md +76 -0
- package/assets/prompts/components/sidebar.md +107 -0
- package/assets/prompts/components/slider.md +89 -0
- package/assets/prompts/components/split-button.md +71 -0
- package/assets/prompts/components/stepper.md +93 -0
- package/assets/prompts/components/switch.md +86 -0
- package/assets/prompts/components/table-cell.md +87 -0
- package/assets/prompts/components/table-header.md +157 -0
- package/assets/prompts/components/table-row-edit.md +129 -0
- package/assets/prompts/components/table-row-select.md +82 -0
- package/assets/prompts/components/table-row.md +156 -0
- package/assets/prompts/components/tabs.md +122 -0
- package/assets/prompts/components/textarea.md +105 -0
- package/assets/prompts/components/toast.md +149 -0
- package/assets/prompts/components/tooltip.md +71 -0
- package/assets/prompts/components/topbar.md +148 -0
- package/assets/prompts/primitives/emerge.md +43 -0
- package/assets/prompts/primitives/fade.md +31 -0
- package/assets/prompts/primitives/scale.md +32 -0
- package/assets/prompts/primitives/scroll.md +75 -0
- package/assets/prompts/primitives/slide.md +31 -0
- package/assets/prompts/themes/_SCHEMA.md +135 -0
- package/assets/prompts/themes/_TEMPLATE.md +73 -0
- package/assets/prompts/themes/expressive.md +118 -0
- package/assets/prompts/themes/pure.md +87 -0
- package/assets/prompts/themes/spare.md +73 -0
- package/assets/prompts/tokens/breakpoint.md +29 -0
- package/assets/prompts/tokens/color.md +78 -0
- package/assets/prompts/tokens/radius.md +33 -0
- package/assets/prompts/tokens/shadow.md +35 -0
- package/assets/prompts/tokens/spacing.md +86 -0
- package/assets/prompts/tokens/typography.md +76 -0
- package/assets/prompts/tokens/z-index.md +35 -0
- package/assets/public/expressive.png +0 -0
- package/assets/public/pure.png +0 -0
- package/assets/public/spare.png +0 -0
- package/assets/skill/SKILL.md +36 -0
- package/bin/ctds.js +76 -0
- package/package.json +35 -4
- package/src/commands/init.js +215 -0
- package/src/commands/update.js +275 -0
- package/src/utils/assets.js +78 -0
- package/src/utils/deps.js +51 -0
- package/src/utils/detect.js +47 -0
- package/src/utils/diff.js +52 -0
- package/src/utils/hash.js +16 -0
- package/src/utils/log.js +41 -0
- package/src/utils/meta.js +73 -0
- package/src/utils/prompt.js +28 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ContextMenu ─ 우클릭/롱프레스로 열리는 컨텍스트 메뉴.
|
|
5
|
+
* Radix `@radix-ui/react-context-menu` 위에 얇게 얹은 compound 컴포넌트.
|
|
6
|
+
*
|
|
7
|
+
* 구조 원칙
|
|
8
|
+
* - 열림/닫힘·포커스 트랩·화살표 네비·서브메뉴 안전지연·뷰포트 flip은 Radix가 담당.
|
|
9
|
+
* - 시각·슬롯·상태(active/checked/destructive/hoverOnly)는 MenuItem이 담당.
|
|
10
|
+
* - 이 파일은 두 계층을 잇는 얇은 어댑터. 신규 시각 규칙 · 로직 · 상태를 여기서 만들지 않는다.
|
|
11
|
+
*
|
|
12
|
+
* 소비 표면
|
|
13
|
+
* <ContextMenu>
|
|
14
|
+
* <ContextMenu.Trigger>...</ContextMenu.Trigger>
|
|
15
|
+
* <ContextMenu.Content>
|
|
16
|
+
* <ContextMenu.Label>제목</ContextMenu.Label>
|
|
17
|
+
* <ContextMenu.Item leadingActions={[<Icon.../>]} shortcut="⌘C" onSelect={...}>
|
|
18
|
+
* 복사
|
|
19
|
+
* </ContextMenu.Item>
|
|
20
|
+
* <ContextMenu.Separator />
|
|
21
|
+
* <ContextMenu.CheckboxItem checked={grid} onCheckedChange={setGrid}>
|
|
22
|
+
* 그리드 표시
|
|
23
|
+
* </ContextMenu.CheckboxItem>
|
|
24
|
+
* <ContextMenu.RadioGroup value={view} onValueChange={setView}>
|
|
25
|
+
* <ContextMenu.RadioItem value="list">리스트</ContextMenu.RadioItem>
|
|
26
|
+
* <ContextMenu.RadioItem value="grid">그리드</ContextMenu.RadioItem>
|
|
27
|
+
* </ContextMenu.RadioGroup>
|
|
28
|
+
* <ContextMenu.Sub>
|
|
29
|
+
* <ContextMenu.SubTrigger>공유</ContextMenu.SubTrigger>
|
|
30
|
+
* <ContextMenu.SubContent>...</ContextMenu.SubContent>
|
|
31
|
+
* </ContextMenu.Sub>
|
|
32
|
+
* <ContextMenu.Item variant="destructive" onSelect={...}>삭제</ContextMenu.Item>
|
|
33
|
+
* </ContextMenu.Content>
|
|
34
|
+
* </ContextMenu>
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import * as RCM from "@radix-ui/react-context-menu";
|
|
38
|
+
import {
|
|
39
|
+
createContext,
|
|
40
|
+
forwardRef,
|
|
41
|
+
useContext,
|
|
42
|
+
type ComponentPropsWithoutRef,
|
|
43
|
+
type ReactNode,
|
|
44
|
+
} from "react";
|
|
45
|
+
|
|
46
|
+
import { Icon, IconAxisProvider, useIconAxis } from "../Icon";
|
|
47
|
+
import { MenuItem, type MenuItemAction, type MenuItemSize, type MenuItemVariant } from "../MenuItem";
|
|
48
|
+
import styles from "./ContextMenu.module.css";
|
|
49
|
+
|
|
50
|
+
/* Size context ─────────────────────────────────────────────────────
|
|
51
|
+
Content/SubContent가 하위 Item·CheckboxItem·RadioItem·SubTrigger의 기본 size를 지정.
|
|
52
|
+
개별 아이템의 size prop이 있으면 그 값이 우선.
|
|
53
|
+
페이지에서 컨테이너 한 번만 size를 지정하면 모든 자식이 따라오도록. */
|
|
54
|
+
const SizeContext = createContext<MenuItemSize>("md");
|
|
55
|
+
function useMenuSize(override?: MenuItemSize): MenuItemSize {
|
|
56
|
+
const ctx = useContext(SizeContext);
|
|
57
|
+
return override ?? ctx;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Root · Trigger · Portal ─────────────────────────────────────────── */
|
|
61
|
+
|
|
62
|
+
type RootProps = ComponentPropsWithoutRef<typeof RCM.Root>;
|
|
63
|
+
const Root = (props: RootProps) => <RCM.Root {...props} />;
|
|
64
|
+
|
|
65
|
+
type TriggerProps = ComponentPropsWithoutRef<typeof RCM.Trigger>;
|
|
66
|
+
const Trigger = forwardRef<HTMLSpanElement, TriggerProps>(function ContextMenuTrigger(props, ref) {
|
|
67
|
+
return <RCM.Trigger ref={ref} {...props} />;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/* Content ─────────────────────────────────────────────────────────── */
|
|
71
|
+
|
|
72
|
+
export interface ContentProps extends ComponentPropsWithoutRef<typeof RCM.Content> {
|
|
73
|
+
/**
|
|
74
|
+
* Content 안의 모든 Item · CheckboxItem · RadioItem · SubTrigger 기본 size.
|
|
75
|
+
* 개별 아이템에서 size를 지정하면 그 값이 우선.
|
|
76
|
+
*/
|
|
77
|
+
size?: MenuItemSize;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const Content = forwardRef<HTMLDivElement, ContentProps>(function ContextMenuContent(
|
|
81
|
+
{ className, size = "md", collisionPadding = 8, children, ...rest },
|
|
82
|
+
ref,
|
|
83
|
+
) {
|
|
84
|
+
/* Radix Portal이 Content를 document.body로 옮기며 React 트리 부모의 Provider가
|
|
85
|
+
흘러가지 않는다. 상위 트리의 Icon 축 값을 캡처해 Portal 하위에 재주입. */
|
|
86
|
+
const iconAxis = useIconAxis();
|
|
87
|
+
return (
|
|
88
|
+
<RCM.Portal>
|
|
89
|
+
<RCM.Content
|
|
90
|
+
ref={ref}
|
|
91
|
+
className={[styles.content, "m-float", className ?? ""].filter(Boolean).join(" ")}
|
|
92
|
+
collisionPadding={collisionPadding}
|
|
93
|
+
data-size={size}
|
|
94
|
+
{...rest}
|
|
95
|
+
>
|
|
96
|
+
<IconAxisProvider value={iconAxis}>
|
|
97
|
+
<SizeContext.Provider value={size}>{children}</SizeContext.Provider>
|
|
98
|
+
</IconAxisProvider>
|
|
99
|
+
</RCM.Content>
|
|
100
|
+
</RCM.Portal>
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/* SubContent ──────────────────────────────────────────────────────── */
|
|
105
|
+
|
|
106
|
+
export interface SubContentProps extends ComponentPropsWithoutRef<typeof RCM.SubContent> {
|
|
107
|
+
size?: MenuItemSize;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const SubContent = forwardRef<HTMLDivElement, SubContentProps>(function ContextMenuSubContent(
|
|
111
|
+
{ className, size, collisionPadding = 8, children, ...rest },
|
|
112
|
+
ref,
|
|
113
|
+
) {
|
|
114
|
+
/* SubContent는 별도 지정이 없으면 부모 Content의 size를 그대로 이어받는다.
|
|
115
|
+
서브메뉴만 다른 사이즈를 쓰는 일이 없어 자연스러운 기본. */
|
|
116
|
+
const parentSize = useContext(SizeContext);
|
|
117
|
+
const effective = size ?? parentSize;
|
|
118
|
+
const iconAxis = useIconAxis();
|
|
119
|
+
return (
|
|
120
|
+
<RCM.Portal>
|
|
121
|
+
<RCM.SubContent
|
|
122
|
+
ref={ref}
|
|
123
|
+
className={[styles.content, "m-float", className ?? ""].filter(Boolean).join(" ")}
|
|
124
|
+
collisionPadding={collisionPadding}
|
|
125
|
+
data-size={effective}
|
|
126
|
+
{...rest}
|
|
127
|
+
>
|
|
128
|
+
<IconAxisProvider value={iconAxis}>
|
|
129
|
+
<SizeContext.Provider value={effective}>{children}</SizeContext.Provider>
|
|
130
|
+
</IconAxisProvider>
|
|
131
|
+
</RCM.SubContent>
|
|
132
|
+
</RCM.Portal>
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/* Label ───────────────────────────────────────────────────────────── */
|
|
137
|
+
|
|
138
|
+
const Label = forwardRef<HTMLDivElement, ComponentPropsWithoutRef<typeof RCM.Label>>(
|
|
139
|
+
function ContextMenuLabel({ className, children, ...rest }, ref) {
|
|
140
|
+
const size = useMenuSize();
|
|
141
|
+
/* MenuItem variant="section"을 감싸서 사이드바 그룹 헤더와 시각 통일.
|
|
142
|
+
Radix Label의 접근성/select 방지는 유지. */
|
|
143
|
+
return (
|
|
144
|
+
<RCM.Label ref={ref} asChild {...rest}>
|
|
145
|
+
<MenuItem variant="section" size={size} className={className}>
|
|
146
|
+
{children}
|
|
147
|
+
</MenuItem>
|
|
148
|
+
</RCM.Label>
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
/* Separator ───────────────────────────────────────────────────────── */
|
|
154
|
+
|
|
155
|
+
const Separator = forwardRef<HTMLDivElement, ComponentPropsWithoutRef<typeof RCM.Separator>>(
|
|
156
|
+
function ContextMenuSeparator({ className, ...rest }, ref) {
|
|
157
|
+
return (
|
|
158
|
+
<RCM.Separator
|
|
159
|
+
ref={ref}
|
|
160
|
+
className={[styles.separator, className ?? ""].filter(Boolean).join(" ")}
|
|
161
|
+
{...rest}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
/* Group · RadioGroup · Sub ───────────────────────────────────────── */
|
|
168
|
+
|
|
169
|
+
const Group = RCM.Group;
|
|
170
|
+
const RadioGroup = RCM.RadioGroup;
|
|
171
|
+
const Sub = RCM.Sub;
|
|
172
|
+
|
|
173
|
+
/* Item ─────────────────────────────────────────────────────────────
|
|
174
|
+
MenuItem을 Radix ContextMenu.Item에 asChild로 위임한다.
|
|
175
|
+
- Radix가 [data-highlighted]/[data-disabled]를 부여 → MenuItem CSS가 그대로 반응.
|
|
176
|
+
- onSelect(Radix)를 노출: 선택 후 자동 닫힘 + 이벤트 preventDefault로 열림 유지 가능. */
|
|
177
|
+
|
|
178
|
+
type ItemPassthrough = {
|
|
179
|
+
size?: MenuItemSize;
|
|
180
|
+
variant?: MenuItemVariant;
|
|
181
|
+
leadingActions?: MenuItemAction[];
|
|
182
|
+
trailingActions?: MenuItemAction[];
|
|
183
|
+
shortcut?: string;
|
|
184
|
+
disabled?: boolean;
|
|
185
|
+
children?: ReactNode;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export interface ItemProps
|
|
189
|
+
extends Omit<ComponentPropsWithoutRef<typeof RCM.Item>, "asChild" | "children">,
|
|
190
|
+
ItemPassthrough {}
|
|
191
|
+
|
|
192
|
+
const Item = forwardRef<HTMLDivElement, ItemProps>(function ContextMenuItem(
|
|
193
|
+
{
|
|
194
|
+
size,
|
|
195
|
+
variant,
|
|
196
|
+
leadingActions,
|
|
197
|
+
trailingActions,
|
|
198
|
+
shortcut,
|
|
199
|
+
disabled,
|
|
200
|
+
children,
|
|
201
|
+
...rest
|
|
202
|
+
},
|
|
203
|
+
ref,
|
|
204
|
+
) {
|
|
205
|
+
const effectiveSize = useMenuSize(size);
|
|
206
|
+
return (
|
|
207
|
+
<RCM.Item asChild disabled={disabled} {...rest}>
|
|
208
|
+
<MenuItem
|
|
209
|
+
ref={ref}
|
|
210
|
+
size={effectiveSize}
|
|
211
|
+
variant={variant}
|
|
212
|
+
leadingActions={leadingActions}
|
|
213
|
+
trailingActions={trailingActions}
|
|
214
|
+
shortcut={shortcut}
|
|
215
|
+
disabled={disabled}
|
|
216
|
+
>
|
|
217
|
+
{children}
|
|
218
|
+
</MenuItem>
|
|
219
|
+
</RCM.Item>
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
/* CheckboxItem ────────────────────────────────────────────────────── */
|
|
224
|
+
|
|
225
|
+
export interface CheckboxItemProps
|
|
226
|
+
extends Omit<ComponentPropsWithoutRef<typeof RCM.CheckboxItem>, "asChild" | "children">,
|
|
227
|
+
ItemPassthrough {}
|
|
228
|
+
|
|
229
|
+
const CheckboxItem = forwardRef<HTMLDivElement, CheckboxItemProps>(function ContextMenuCheckboxItem(
|
|
230
|
+
{
|
|
231
|
+
size,
|
|
232
|
+
variant,
|
|
233
|
+
leadingActions = [],
|
|
234
|
+
trailingActions,
|
|
235
|
+
shortcut,
|
|
236
|
+
disabled,
|
|
237
|
+
checked,
|
|
238
|
+
onCheckedChange,
|
|
239
|
+
children,
|
|
240
|
+
...rest
|
|
241
|
+
},
|
|
242
|
+
ref,
|
|
243
|
+
) {
|
|
244
|
+
const effectiveSize = useMenuSize(size);
|
|
245
|
+
/* leading 맨 앞에 Checkbox 컴포넌트의 시각 인디케이터를 얹는다.
|
|
246
|
+
Radix CheckboxItem이 상태·포커스·키보드를 이미 관리하므로 여기선 시각만 필요 —
|
|
247
|
+
Checkbox 컴포넌트의 <label><input> 접근성 구조는 중첩되면 방해되므로 재사용하지 않고,
|
|
248
|
+
`.indicator` 스타일만 composes로 가져와 span 하나로 재현한다.
|
|
249
|
+
안에 항상 check Icon을 두되, Checkbox CSS의 `color: transparent → var(--bg)`가
|
|
250
|
+
checked일 때만 색을 드러낸다(data-checked). */
|
|
251
|
+
const indicatorSlot: MenuItemAction = {
|
|
252
|
+
node: (
|
|
253
|
+
<span
|
|
254
|
+
className={styles.checkIndicator}
|
|
255
|
+
data-checked={checked === true ? "true" : undefined}
|
|
256
|
+
aria-hidden
|
|
257
|
+
>
|
|
258
|
+
<Icon name="check" />
|
|
259
|
+
</span>
|
|
260
|
+
),
|
|
261
|
+
key: "__checkbox__",
|
|
262
|
+
};
|
|
263
|
+
const composedLeading: MenuItemAction[] = [indicatorSlot, ...leadingActions];
|
|
264
|
+
return (
|
|
265
|
+
<RCM.CheckboxItem
|
|
266
|
+
asChild
|
|
267
|
+
disabled={disabled}
|
|
268
|
+
checked={checked}
|
|
269
|
+
onCheckedChange={onCheckedChange}
|
|
270
|
+
{...rest}
|
|
271
|
+
>
|
|
272
|
+
<MenuItem
|
|
273
|
+
ref={ref}
|
|
274
|
+
size={effectiveSize}
|
|
275
|
+
variant={variant}
|
|
276
|
+
leadingActions={composedLeading}
|
|
277
|
+
trailingActions={trailingActions}
|
|
278
|
+
shortcut={shortcut}
|
|
279
|
+
disabled={disabled}
|
|
280
|
+
>
|
|
281
|
+
{children}
|
|
282
|
+
</MenuItem>
|
|
283
|
+
</RCM.CheckboxItem>
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
/* RadioItem ───────────────────────────────────────────────────────── */
|
|
288
|
+
|
|
289
|
+
export interface RadioItemProps
|
|
290
|
+
extends Omit<ComponentPropsWithoutRef<typeof RCM.RadioItem>, "asChild" | "children">,
|
|
291
|
+
ItemPassthrough {}
|
|
292
|
+
|
|
293
|
+
const RadioItem = forwardRef<HTMLDivElement, RadioItemProps>(function ContextMenuRadioItem(
|
|
294
|
+
{
|
|
295
|
+
size,
|
|
296
|
+
variant,
|
|
297
|
+
leadingActions = [],
|
|
298
|
+
trailingActions,
|
|
299
|
+
shortcut,
|
|
300
|
+
disabled,
|
|
301
|
+
children,
|
|
302
|
+
...rest
|
|
303
|
+
},
|
|
304
|
+
ref,
|
|
305
|
+
) {
|
|
306
|
+
const effectiveSize = useMenuSize(size);
|
|
307
|
+
/* leading 맨 앞에 Radio 컴포넌트의 시각 인디케이터를 얹는다.
|
|
308
|
+
Radix RadioItem이 상태·포커스·키보드를 이미 관리하므로 여기선 시각만 필요.
|
|
309
|
+
`.indicator` 스타일만 composes로 가져와 span 하나로 재현. checked 여부는
|
|
310
|
+
Radix가 asChild 대상 MenuItem에 붙이는 data-state="checked"를 CSS 컨텍스트
|
|
311
|
+
셀렉터로 잡아 자손 indicator에 전파한다 (module.css 참고). */
|
|
312
|
+
const composedLeading: MenuItemAction[] = [
|
|
313
|
+
{ node: <span className={styles.radioIndicator} aria-hidden />, key: "__radio__" },
|
|
314
|
+
...leadingActions,
|
|
315
|
+
];
|
|
316
|
+
return (
|
|
317
|
+
<RCM.RadioItem asChild disabled={disabled} {...rest}>
|
|
318
|
+
<MenuItem
|
|
319
|
+
ref={ref}
|
|
320
|
+
size={effectiveSize}
|
|
321
|
+
variant={variant}
|
|
322
|
+
leadingActions={composedLeading}
|
|
323
|
+
trailingActions={trailingActions}
|
|
324
|
+
shortcut={shortcut}
|
|
325
|
+
disabled={disabled}
|
|
326
|
+
>
|
|
327
|
+
{children}
|
|
328
|
+
</MenuItem>
|
|
329
|
+
</RCM.RadioItem>
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
/* SubTrigger ──────────────────────────────────────────────────────── */
|
|
334
|
+
|
|
335
|
+
export interface SubTriggerProps
|
|
336
|
+
extends Omit<ComponentPropsWithoutRef<typeof RCM.SubTrigger>, "asChild" | "children">,
|
|
337
|
+
Omit<ItemPassthrough, "shortcut"> {}
|
|
338
|
+
|
|
339
|
+
const SubTrigger = forwardRef<HTMLDivElement, SubTriggerProps>(function ContextMenuSubTrigger(
|
|
340
|
+
{ size, variant, leadingActions, trailingActions, disabled, children, ...rest },
|
|
341
|
+
ref,
|
|
342
|
+
) {
|
|
343
|
+
const effectiveSize = useMenuSize(size);
|
|
344
|
+
return (
|
|
345
|
+
<RCM.SubTrigger asChild disabled={disabled} {...rest}>
|
|
346
|
+
<MenuItem
|
|
347
|
+
ref={ref}
|
|
348
|
+
size={effectiveSize}
|
|
349
|
+
variant={variant}
|
|
350
|
+
leadingActions={leadingActions}
|
|
351
|
+
trailingActions={trailingActions}
|
|
352
|
+
hasSubmenu
|
|
353
|
+
disabled={disabled}
|
|
354
|
+
>
|
|
355
|
+
{children}
|
|
356
|
+
</MenuItem>
|
|
357
|
+
</RCM.SubTrigger>
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
/* Compound export ─────────────────────────────────────────────────── */
|
|
362
|
+
|
|
363
|
+
type ContextMenuComponent = typeof Root & {
|
|
364
|
+
Trigger: typeof Trigger;
|
|
365
|
+
Content: typeof Content;
|
|
366
|
+
Item: typeof Item;
|
|
367
|
+
CheckboxItem: typeof CheckboxItem;
|
|
368
|
+
RadioGroup: typeof RadioGroup;
|
|
369
|
+
RadioItem: typeof RadioItem;
|
|
370
|
+
Separator: typeof Separator;
|
|
371
|
+
Label: typeof Label;
|
|
372
|
+
Group: typeof Group;
|
|
373
|
+
Sub: typeof Sub;
|
|
374
|
+
SubTrigger: typeof SubTrigger;
|
|
375
|
+
SubContent: typeof SubContent;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const ContextMenu = Root as ContextMenuComponent;
|
|
379
|
+
ContextMenu.Trigger = Trigger;
|
|
380
|
+
ContextMenu.Content = Content;
|
|
381
|
+
ContextMenu.Item = Item;
|
|
382
|
+
ContextMenu.CheckboxItem = CheckboxItem;
|
|
383
|
+
ContextMenu.RadioGroup = RadioGroup;
|
|
384
|
+
ContextMenu.RadioItem = RadioItem;
|
|
385
|
+
ContextMenu.Separator = Separator;
|
|
386
|
+
ContextMenu.Label = Label;
|
|
387
|
+
ContextMenu.Group = Group;
|
|
388
|
+
ContextMenu.Sub = Sub;
|
|
389
|
+
ContextMenu.SubTrigger = SubTrigger;
|
|
390
|
+
ContextMenu.SubContent = SubContent;
|
|
391
|
+
|
|
392
|
+
export { ContextMenu };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { ContextMenu } from "./ContextMenu";
|
|
2
|
+
export type {
|
|
3
|
+
ContentProps as ContextMenuContentProps,
|
|
4
|
+
SubContentProps as ContextMenuSubContentProps,
|
|
5
|
+
ItemProps as ContextMenuItemProps,
|
|
6
|
+
CheckboxItemProps as ContextMenuCheckboxItemProps,
|
|
7
|
+
RadioItemProps as ContextMenuRadioItemProps,
|
|
8
|
+
SubTriggerProps as ContextMenuSubTriggerProps,
|
|
9
|
+
} from "./ContextMenu";
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { memo, useMemo } from "react";
|
|
4
|
+
import { IconButton } from "../IconButton";
|
|
5
|
+
import { Icon } from "../Icon";
|
|
6
|
+
import styles from "./DatePicker.module.css";
|
|
7
|
+
import {
|
|
8
|
+
addMonths,
|
|
9
|
+
buildMonthGrid,
|
|
10
|
+
isSameDay,
|
|
11
|
+
isSameMonth,
|
|
12
|
+
isWithin,
|
|
13
|
+
MONTH_LABELS_KO,
|
|
14
|
+
WEEKDAY_LABELS_KO,
|
|
15
|
+
type DateRange,
|
|
16
|
+
} from "./utils";
|
|
17
|
+
|
|
18
|
+
interface MonthProps {
|
|
19
|
+
month: Date;
|
|
20
|
+
selectedFrom: Date | null;
|
|
21
|
+
selectedTo: Date | null;
|
|
22
|
+
hoverDate: Date | null;
|
|
23
|
+
rangeInProgress: boolean;
|
|
24
|
+
mode: "single" | "range";
|
|
25
|
+
onSelectDay: (day: Date) => void;
|
|
26
|
+
onHoverDay: (day: Date | null) => void;
|
|
27
|
+
minDate?: Date;
|
|
28
|
+
maxDate?: Date;
|
|
29
|
+
weekStartsOn: number;
|
|
30
|
+
showHeader: boolean;
|
|
31
|
+
onPrev?: () => void;
|
|
32
|
+
onNext?: () => void;
|
|
33
|
+
showPrev?: boolean;
|
|
34
|
+
showNext?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const MonthView = memo(function MonthView({
|
|
38
|
+
month,
|
|
39
|
+
selectedFrom,
|
|
40
|
+
selectedTo,
|
|
41
|
+
hoverDate,
|
|
42
|
+
rangeInProgress,
|
|
43
|
+
mode,
|
|
44
|
+
onSelectDay,
|
|
45
|
+
onHoverDay,
|
|
46
|
+
minDate,
|
|
47
|
+
maxDate,
|
|
48
|
+
weekStartsOn,
|
|
49
|
+
showHeader,
|
|
50
|
+
onPrev,
|
|
51
|
+
onNext,
|
|
52
|
+
showPrev,
|
|
53
|
+
showNext,
|
|
54
|
+
}: MonthProps) {
|
|
55
|
+
const cells = useMemo(
|
|
56
|
+
() => buildMonthGrid(month, weekStartsOn),
|
|
57
|
+
[month, weekStartsOn]
|
|
58
|
+
);
|
|
59
|
+
const weekdays = useMemo(() => {
|
|
60
|
+
const list = [...WEEKDAY_LABELS_KO];
|
|
61
|
+
for (let i = 0; i < weekStartsOn; i += 1) list.push(list.shift() as string);
|
|
62
|
+
return list;
|
|
63
|
+
}, [weekStartsOn]);
|
|
64
|
+
|
|
65
|
+
const previewTo = rangeInProgress && selectedFrom && !selectedTo ? hoverDate : null;
|
|
66
|
+
const rangeStart = selectedFrom;
|
|
67
|
+
const rangeEnd = selectedTo ?? previewTo;
|
|
68
|
+
const normalizedStart =
|
|
69
|
+
rangeStart && rangeEnd && rangeStart.getTime() > rangeEnd.getTime() ? rangeEnd : rangeStart;
|
|
70
|
+
const normalizedEnd =
|
|
71
|
+
rangeStart && rangeEnd && rangeStart.getTime() > rangeEnd.getTime() ? rangeStart : rangeEnd;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className={styles.monthCol}>
|
|
75
|
+
{showHeader && (
|
|
76
|
+
<div className={styles.monthHeader}>
|
|
77
|
+
<IconButton
|
|
78
|
+
variant="outline"
|
|
79
|
+
size="md"
|
|
80
|
+
icon={<Icon name="caret-left" />}
|
|
81
|
+
onClick={onPrev}
|
|
82
|
+
aria-label="이전 달"
|
|
83
|
+
style={{ visibility: showPrev ? "visible" : "hidden" }}
|
|
84
|
+
/>
|
|
85
|
+
<div className={styles.monthTitle}>
|
|
86
|
+
{month.getFullYear()}년 {MONTH_LABELS_KO[month.getMonth()]}
|
|
87
|
+
</div>
|
|
88
|
+
<IconButton
|
|
89
|
+
variant="outline"
|
|
90
|
+
size="md"
|
|
91
|
+
icon={<Icon name="caret-right" />}
|
|
92
|
+
onClick={onNext}
|
|
93
|
+
aria-label="다음 달"
|
|
94
|
+
style={{ visibility: showNext ? "visible" : "hidden" }}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
<div className={styles.weekdays} role="row">
|
|
100
|
+
{weekdays.map((w) => (
|
|
101
|
+
<div key={w} role="columnheader" className={styles.weekday}>
|
|
102
|
+
{w}
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className={styles.grid} role="grid">
|
|
108
|
+
{cells.map((day) => {
|
|
109
|
+
const outside = !isSameMonth(day, month);
|
|
110
|
+
const disabled =
|
|
111
|
+
outside ||
|
|
112
|
+
(minDate && day.getTime() < minDate.setHours(0, 0, 0, 0)) ||
|
|
113
|
+
(maxDate && day.getTime() > maxDate.setHours(23, 59, 59, 999));
|
|
114
|
+
|
|
115
|
+
const isFrom = mode === "range" && isSameDay(day, normalizedStart);
|
|
116
|
+
const isTo = mode === "range" && isSameDay(day, normalizedEnd);
|
|
117
|
+
const isSelected =
|
|
118
|
+
mode === "single"
|
|
119
|
+
? isSameDay(day, selectedFrom)
|
|
120
|
+
: isFrom || isTo;
|
|
121
|
+
const inRange =
|
|
122
|
+
mode === "range" &&
|
|
123
|
+
!outside &&
|
|
124
|
+
normalizedStart &&
|
|
125
|
+
normalizedEnd &&
|
|
126
|
+
isWithin(day, normalizedStart, normalizedEnd) &&
|
|
127
|
+
!isFrom &&
|
|
128
|
+
!isTo;
|
|
129
|
+
const isToday = !outside && isSameDay(day, new Date());
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
key={day.toISOString()}
|
|
135
|
+
className={styles.dayCell}
|
|
136
|
+
data-outside={outside || undefined}
|
|
137
|
+
data-today={isToday || undefined}
|
|
138
|
+
data-selected={isSelected || undefined}
|
|
139
|
+
data-in-range={inRange || undefined}
|
|
140
|
+
data-range-start={isFrom || undefined}
|
|
141
|
+
data-range-end={isTo || undefined}
|
|
142
|
+
disabled={disabled || undefined}
|
|
143
|
+
onClick={() => onSelectDay(day)}
|
|
144
|
+
onMouseEnter={() => onHoverDay(day)}
|
|
145
|
+
onMouseLeave={() => onHoverDay(null)}
|
|
146
|
+
aria-selected={isSelected}
|
|
147
|
+
role="gridcell"
|
|
148
|
+
>
|
|
149
|
+
<span className={styles.dayLabel}>{day.getDate()}</span>
|
|
150
|
+
</button>
|
|
151
|
+
);
|
|
152
|
+
})}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
export interface CalendarProps {
|
|
159
|
+
mode: "single" | "range";
|
|
160
|
+
months: 1 | 2;
|
|
161
|
+
viewMonth: Date;
|
|
162
|
+
onViewMonthChange: (m: Date) => void;
|
|
163
|
+
selectedFrom: Date | null;
|
|
164
|
+
selectedTo: Date | null;
|
|
165
|
+
hoverDate: Date | null;
|
|
166
|
+
onHoverDate: (d: Date | null) => void;
|
|
167
|
+
rangeInProgress: boolean;
|
|
168
|
+
onSelectDay: (day: Date) => void;
|
|
169
|
+
minDate?: Date;
|
|
170
|
+
maxDate?: Date;
|
|
171
|
+
weekStartsOn?: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function Calendar({
|
|
175
|
+
mode,
|
|
176
|
+
months,
|
|
177
|
+
viewMonth,
|
|
178
|
+
onViewMonthChange,
|
|
179
|
+
selectedFrom,
|
|
180
|
+
selectedTo,
|
|
181
|
+
hoverDate,
|
|
182
|
+
onHoverDate,
|
|
183
|
+
rangeInProgress,
|
|
184
|
+
onSelectDay,
|
|
185
|
+
minDate,
|
|
186
|
+
maxDate,
|
|
187
|
+
weekStartsOn = 0,
|
|
188
|
+
}: CalendarProps) {
|
|
189
|
+
const secondMonth = addMonths(viewMonth, 1);
|
|
190
|
+
return (
|
|
191
|
+
<div className={styles.calendarWrap} data-months={months}>
|
|
192
|
+
<MonthView
|
|
193
|
+
month={viewMonth}
|
|
194
|
+
selectedFrom={selectedFrom}
|
|
195
|
+
selectedTo={selectedTo}
|
|
196
|
+
hoverDate={hoverDate}
|
|
197
|
+
rangeInProgress={rangeInProgress}
|
|
198
|
+
mode={mode}
|
|
199
|
+
onSelectDay={onSelectDay}
|
|
200
|
+
onHoverDay={onHoverDate}
|
|
201
|
+
minDate={minDate}
|
|
202
|
+
maxDate={maxDate}
|
|
203
|
+
weekStartsOn={weekStartsOn}
|
|
204
|
+
showHeader
|
|
205
|
+
onPrev={() => onViewMonthChange(addMonths(viewMonth, -1))}
|
|
206
|
+
onNext={() => onViewMonthChange(addMonths(viewMonth, 1))}
|
|
207
|
+
showPrev
|
|
208
|
+
showNext={months === 1}
|
|
209
|
+
/>
|
|
210
|
+
{months === 2 && (
|
|
211
|
+
<MonthView
|
|
212
|
+
month={secondMonth}
|
|
213
|
+
selectedFrom={selectedFrom}
|
|
214
|
+
selectedTo={selectedTo}
|
|
215
|
+
hoverDate={hoverDate}
|
|
216
|
+
rangeInProgress={rangeInProgress}
|
|
217
|
+
mode={mode}
|
|
218
|
+
onSelectDay={onSelectDay}
|
|
219
|
+
onHoverDay={onHoverDate}
|
|
220
|
+
minDate={minDate}
|
|
221
|
+
maxDate={maxDate}
|
|
222
|
+
weekStartsOn={weekStartsOn}
|
|
223
|
+
showHeader
|
|
224
|
+
onPrev={() => onViewMonthChange(addMonths(viewMonth, -1))}
|
|
225
|
+
onNext={() => onViewMonthChange(addMonths(viewMonth, 1))}
|
|
226
|
+
showPrev={false}
|
|
227
|
+
showNext
|
|
228
|
+
/>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export type { DateRange };
|