From 74cf6889b84b78d2900df9dda941aa48f509fdee Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 17 Feb 2025 11:35:28 +0100 Subject: [PATCH] updated dialog and calendar --- package-lock.json | 88 +++++++++++++++++++ package.json | 2 + src/app/calendar/page.tsx | 109 ++++++++++++----------- src/calendar-theme.css | 101 +++++++++------------ src/components/postDialog.tsx | 101 +++++++++++++++++++++ src/components/timeSelect.tsx | 68 +++++++++++++++ src/components/ui/popover.tsx | 33 +++++++ src/components/ui/select.tsx | 159 ++++++++++++++++++++++++++++++++++ 8 files changed, 548 insertions(+), 113 deletions(-) create mode 100644 src/components/postDialog.tsx create mode 100644 src/components/timeSelect.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/select.tsx diff --git a/package-lock.json b/package-lock.json index d818a57..b2fbe35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@radix-ui/react-context-menu": "^2.2.6", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", @@ -761,6 +763,12 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", @@ -1103,6 +1111,43 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz", + "integrity": "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", @@ -1237,6 +1282,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.6.tgz", + "integrity": "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.2.tgz", diff --git a/package.json b/package.json index 310523f..0a01de9 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "@radix-ui/react-context-menu": "^2.2.6", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", diff --git a/src/app/calendar/page.tsx b/src/app/calendar/page.tsx index e907ee4..40095fa 100644 --- a/src/app/calendar/page.tsx +++ b/src/app/calendar/page.tsx @@ -1,17 +1,12 @@ 'use client'; import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react'; -import { - createViewDay, - createViewMonthAgenda, - createViewMonthGrid, - createViewWeek, -} from '@schedule-x/calendar'; +import { createViewWeek } from '@schedule-x/calendar'; import { createEventsServicePlugin } from '@schedule-x/events-service'; import { createDragAndDropPlugin } from '@schedule-x/drag-and-drop'; import '@/calendar-theme.css'; -import { useEffect, useState } from 'react'; +import { MouseEventHandler, useEffect, useState } from 'react'; import App from '@/components/app'; import { ContextMenu, @@ -21,26 +16,16 @@ import { } from '@/components/ui/context-menu'; import { PlusCircle } from 'lucide-react'; -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; + +import { parse } from 'date-fns'; +import PostDialog, { today } from '@/components/postDialog'; function CalendarApp() { const eventsService = useState(() => createEventsServicePlugin())[0]; const dragAndDrop = useState(() => createDragAndDropPlugin())[0]; const calendar = useCalendarApp({ - views: [ - createViewDay(), - createViewWeek(), - createViewMonthGrid(), - createViewMonthAgenda(), - ], + views: [createViewWeek()], events: [ { id: '1', @@ -58,38 +43,64 @@ function CalendarApp() { }, [eventsService]); const [newPostDialogOpen, setNewPostDialogOpen] = useState(false); + const [date, setDate] = useState(today); + + const handleDialog: MouseEventHandler = (event) => { + const target = event.target as HTMLElement; + + let hourElement = null; + let closestDistance = Infinity; + const children = target.parentElement?.children[0].children || []; + + for (let child of children) { + const childRect = child.getBoundingClientRect(); + const dx = Math.abs( + event.clientX - (childRect.left + childRect.width / 2) + ); + const dy = Math.abs( + event.clientY - (childRect.top + childRect.height / 2) + ); + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < closestDistance) { + closestDistance = distance; + hourElement = child; + } + } + + const dayElement = target.closest('.sx__time-grid-day'); + + const dayText = dayElement + ? dayElement.getAttribute('aria-label') + : null; + + if (!dayText || !hourElement) { + event.preventDefault(); + } else { + setDate( + parse( + (dayText || '') + ' ' + (hourElement?.textContent || ''), + 'MMMM dd, yyyy h a', + new Date() + ) + ); + } + }; return (
- - - - Feature not available - -
- - - - -
-
- + - + @@ -99,7 +110,7 @@ function CalendarApp() { }} > {' '} -

New post

+

New post

x
@@ -108,8 +119,4 @@ function CalendarApp() { ); } -function CalendarHeader() { - return
; -} - export default CalendarApp; diff --git a/src/calendar-theme.css b/src/calendar-theme.css index cdbeadc..0c1f6be 100644 --- a/src/calendar-theme.css +++ b/src/calendar-theme.css @@ -14,66 +14,43 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ :root { - --sx-color-primary: #6750a4; - --sx-color-on-primary: #fff; - --sx-color-primary-container: #eaddff; - --sx-color-on-primary-container: #21005e; - --sx-color-secondary: #625b71; - --sx-color-on-secondary: #fff; - --sx-color-secondary-container: #e8def8; - --sx-color-on-secondary-container: #1e192b; - --sx-color-tertiary: #7d5260; - --sx-color-on-tertiary: #fff; - --sx-color-tertiary-container: #ffd8e4; - --sx-color-on-tertiary-container: #370b1e; - --sx-color-surface: #fef7ff; - --sx-color-surface-dim: #ded8e1; - --sx-color-surface-bright: #fef7ff; - --sx-color-on-surface: #1c1b1f; - --sx-color-surface-container: #f3edf7; - --sx-color-surface-container-low: #f7f2fa; - --sx-color-surface-container-high: #ece6f0; - --sx-color-background: #fff; - --sx-color-on-background: #1c1b1f; - --sx-color-outline: #79747e; - --sx-color-outline-variant: #c4c7c5; - --sx-color-shadow: #000; - --sx-color-surface-tint: #6750a4; - --sx-color-neutral: var(--sx-color-outline); - --sx-color-neutral-variant: var(--sx-color-outline-variant); - --sx-internal-color-gray-ripple-background: #e0e0e0; - --sx-internal-color-light-gray: #fafafa; - --sx-internal-color-text: #000; + --sx-color-primary: hsl(var(--primary)); + --sx-color-on-primary: hsl(var(--primary-foreground)); + --sx-color-primary-container: hsl(var(--secondary)); + --sx-color-on-primary-container: hsl(var(--secondary-foreground)); + + --sx-color-secondary: hsl(var(--muted)); + --sx-color-on-secondary: hsl(var(--muted-foreground)); + --sx-color-secondary-container: hsl(var(--accent)); + --sx-color-on-secondary-container: hsl(var(--accent-foreground)); + + --sx-color-tertiary: hsl(var(--chart-1)); + --sx-color-on-tertiary: hsl(var(--foreground)); + --sx-color-tertiary-container: hsl(var(--chart-2)); + --sx-color-on-tertiary-container: hsl(var(--foreground)); + + --sx-color-surface: hsl(var(--background)); + --sx-color-surface-dim: hsl(var(--card)); + --sx-color-surface-bright: hsl(var(--popover)); + --sx-color-on-surface: hsl(var(--foreground)); + + --sx-color-surface-container: hsl(var(--card)); + --sx-color-surface-container-low: hsl(var(--popover)); + --sx-color-surface-container-high: hsl(var(--secondary)); + + --sx-color-on-background: hsl(var(--foreground)); + --sx-color-background: hsl(var(--background)); + + --sx-color-outline: hsl(var(--border)); + --sx-color-outline-variant: hsl(var(--input)); + + --sx-color-shadow: hsl(var(--background)); + --sx-color-surface-tint: var(--sx-color-primary); + + --sx-internal-color-text: hsl(var(--foreground)); } -.is-dark { - --sx-color-primary: #d0bcff; - --sx-color-on-primary: #371e73; - --sx-color-primary-container: #4f378b; - --sx-color-on-primary-container: #eaddff; - --sx-color-secondary: #ccc2dc; - --sx-color-on-secondary: #332d41; - --sx-color-secondary-container: #4a4458; - --sx-color-on-secondary-container: #e8def8; - --sx-color-tertiary: #efb8c8; - --sx-color-on-tertiary: #492532; - --sx-color-tertiary-container: #633b48; - --sx-color-on-tertiary-container: #ffd8e4; - --sx-color-surface: #141218; - --sx-color-surface-dim: #141218; - --sx-color-surface-bright: #3b383e; - --sx-color-on-surface: #e6e1e5; - --sx-color-surface-container: #211f26; - --sx-color-surface-container-low: #1d1b20; - --sx-color-surface-container-high: #2b2930; - --sx-color-background: #141218; - --sx-color-on-background: #e6e1e5; - --sx-color-outline: #938f99; - --sx-color-outline-variant: #444746; - --sx-color-shadow: #000; - --sx-color-surface-tint: #d0bcff; - --sx-internal-color-text: #fff; -} +/* .is-dark {} */ :root { --sx-spacing-padding1: 4px; @@ -90,7 +67,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --sx-rounding-extra-small: 4px; --sx-rounding-small: 8px; --sx-rounding-extra-large: 28px; - --sx-border: 1px solid var(--sx-color-outline-variant); + /* --sx-border: 1px solid var(--sx-color-outline-variant); */ } .is-dark { @@ -497,7 +474,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI position: relative; flex: 1; height: 100%; - border: var(--sx-border); + /* border: var(--sx-border); */ /* Disabled this to make it look cleaner*/ border-radius: var(--sx-rounding-small); display: flex; flex-flow: column; @@ -513,7 +490,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI } .sx__slide-left { - animation: sx-slide-left 0.3s ease-out; + /* animation: sx-slide-left 0.3s ease-out; */ } @keyframes sx-slide-left { @@ -529,7 +506,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI } } .sx__slide-right { - animation: sx-slide-right 0.3s ease-out; + /* animation: sx-slide-right 0.3s ease-out; */ } @keyframes sx-slide-right { diff --git a/src/components/postDialog.tsx b/src/components/postDialog.tsx new file mode 100644 index 0000000..c781cb5 --- /dev/null +++ b/src/components/postDialog.tsx @@ -0,0 +1,101 @@ +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; + +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; +import { format } from 'date-fns'; +import { CalendarIcon } from 'lucide-react'; +import { Calendar } from './ui/calendar'; +import TimeSelect from './timeSelect'; + +type PostDialogProps = { + newPostDialogOpen: boolean; + setNewPostDialogOpen: (b: boolean) => void; + date: Date; + setDate: (d: Date) => void; +}; + +export const today = new Date(); +today.setHours(0, 0, 0, 0); + +export default function PostDialog({ + newPostDialogOpen, + setNewPostDialogOpen, + date, + setDate, +}: PostDialogProps) { + return ( + + + + Feature not available + +
+ + + + + + { + if (d) { + const newDate = new Date(d); // Clone d + newDate.setHours( + date.getHours(), + date.getMinutes(), + 0, + 0 + ); + setDate(newDate); + } + }} + mode="single" + disabled={(date) => date < today} + /> + + + + +
+ + + + + +
+
+ ); +} diff --git a/src/components/timeSelect.tsx b/src/components/timeSelect.tsx new file mode 100644 index 0000000..837519c --- /dev/null +++ b/src/components/timeSelect.tsx @@ -0,0 +1,68 @@ +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; + +interface TimeSelectProps { + date: Date; + setDate: (newDate: Date) => void; + className?: string; +} + +const TimeSelect: React.FC = ({ + date, + setDate, + className, +}) => { + // Generate time options for all hours (00-23) with minutes (15, 30, 45) + const times = Array.from({ length: 24 }, (_, h) => + [15, 30, 45].map((m) => `${h.toString().padStart(2, '0')}:${m}`) + ).flat(); + + const currentHour = date.getHours().toString().padStart(2, '0'); + const currentMinute = date.getMinutes(); + const nearestMinute = [15, 30, 45].reduce((prev, curr) => + Math.abs(curr - currentMinute) < Math.abs(prev - currentMinute) + ? curr + : prev + ); + const selectedTime = `${currentHour}:${nearestMinute}`; + + const handleTimeChange = (newTime: string) => { + const [hour, minute] = newTime.split(':').map(Number); + const newDate = new Date(date); + newDate.setHours(hour, minute, 0, 0); + setDate(newDate); + }; + + return ( +
+ +
+ ); +}; + +export default TimeSelect; diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..29c7bd2 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,33 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..0cbf77d --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,159 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}