Skip to content

Commit

Permalink
feat: time selection (#30)
Browse files Browse the repository at this point in the history
* feat: added time selection for availability

* feat: added time selection for availability

* prettierignore update
  • Loading branch information
jxmoose authored Dec 4, 2024
1 parent 194f5fc commit c33d5a0
Show file tree
Hide file tree
Showing 7 changed files with 1,703 additions and 2,096 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
README.md
package-lock.json
next-env.d.ts
pnpm-lock.yaml
49 changes: 49 additions & 0 deletions components/DateTimeSelection/DateTimeSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState } from 'react';
import TimeSelection from '@/components/TimeSelection/TimeSelection';
import COLORS from '@/styles/colors';
import { H6, SMALL } from '@/styles/text';
import { AddTime, Bar, Container, TimeList, Times } from './styles';

export default function DateTimeSelection({ date }: { date: Date }) {
const [timeList, setTimeList] = useState([
<Times key={0}>
{/* initialize at 9 am / 5 pm */}
<TimeSelection minutes={9 * 60} />
<H6 $color={COLORS.gray8}> - </H6>
<TimeSelection minutes={17 * 60} />
</Times>,
]);

const onAddBtnClick = () => {
setTimeList(
timeList.concat(
<Times>
{/* initialize at 9 am / 5 pm */}
<TimeSelection minutes={9 * 60} />
<H6 $color={COLORS.gray8}> - </H6>
<TimeSelection minutes={17 * 60} />
</Times>,
),
);
};

return (
<Container>
<H6 $fontWeight={500}>
{date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'long',
day: 'numeric',
year: 'numeric',
})}
</H6>
<Bar />
<TimeList> {timeList}</TimeList>
<AddTime onClick={() => onAddBtnClick()}>
<SMALL $color={COLORS.lilac8} $fontWeight={400}>
add time
</SMALL>
</AddTime>
</Container>
);
}
35 changes: 35 additions & 0 deletions components/DateTimeSelection/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import styled from 'styled-components';
import COLORS from '@/styles/colors';

export const Container = styled.div`
padding: 2rem;
`;

export const Bar = styled.hr`
height: 1px;
color: ${COLORS.gray6};
background: ${COLORS.gray6};
font-size: 0;
border: 0;
margin-top: 0.5rem;
margin-bottom: 1rem;
`;

export const Times = styled.div`
display: flex;
flex-direction: horizontal;
gap: 0.5rem;
align-items: center;
`;

export const AddTime = styled.button`
all: unset;
padding-top: 1rem;
padding-bottom: 1rem;
`;

export const TimeList = styled.div`
display: flex;
flex-direction: column;
gap: 0.5rem;
`;
91 changes: 91 additions & 0 deletions components/TimeSelection/TimeSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import { CheckIcon, ChevronDownIcon } from '@radix-ui/react-icons';
import * as ScrollArea from '@radix-ui/react-scroll-area';
import * as Select from '@radix-ui/react-select';
import classnames from 'classnames';
import { Container, Content } from './styles';

export default function TimeSelection({ minutes }: { minutes: number }) {
const minutesToFormatted = (minutes: number) => {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
const period = hours >= 12 ? 'PM' : 'AM';
const formattedHours = hours > 12 ? hours - 12 : hours;
return `${formattedHours}:${mins.toString().padStart(2, '0')} ${period}`;
};

const generateTimeSlots = () => {
const times = [];
const startTime = 9 * 60; // 9:00 AM in minutes
const endTime = 20 * 60; // 5:00 PM in minutes

for (let minutes = startTime; minutes <= endTime; minutes += 30) {
times.push([minutes.toString(), minutesToFormatted(minutes)]);
}

return times;
};

const times = generateTimeSlots();

return (
<Container>
<Select.Root defaultValue={minutes.toString()}>
<Select.Trigger className="SelectTrigger" aria-label="Food">
<Select.Value placeholder={minutesToFormatted(minutes)} />
<Select.Icon className="SelectIcon">
<ChevronDownIcon />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Content>
<Select.Content className="SelectContent">
<ScrollArea.Root className="Scroll" type="auto">
<Select.Viewport asChild className="SelectViewport">
<ScrollArea.Viewport className="ScrollViewport">
<Select.Group className="group">
{times.map((time, i) => {
const minutes = time[0];
const formattedTime = time[1];
return (
<SelectItem key={i} value={minutes}>
{formattedTime}
</SelectItem>
);
})}
</Select.Group>
</ScrollArea.Viewport>
</Select.Viewport>
</ScrollArea.Root>
</Select.Content>
</Content>
</Select.Portal>
</Select.Root>
</Container>
);
}

interface SelectItemProps
extends React.ComponentPropsWithoutRef<typeof Select.Item> {
children: React.ReactNode;
className?: string;
}

const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(
({ children, className, ...props }, forwardedRef) => {
return (
<Select.Item
className={classnames('SelectItem', className)}
{...props}
ref={forwardedRef}
>
<Select.ItemIndicator className="SelectItemIndicator">
<CheckIcon />
</Select.ItemIndicator>
<Select.ItemText>{children}</Select.ItemText>
</Select.Item>
);
},
);

SelectItem.displayName = 'test';
102 changes: 102 additions & 0 deletions components/TimeSelection/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import styled from 'styled-components';
import COLORS from '@/styles/colors';

export const Container = styled.div`
button {
all: unset;
}
.SelectTrigger {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 8px;
padding: 0 15px;
font-size: 13px;
line-height: 1;
height: 35px;
gap: 5px;
background-color: white;
color = ${COLORS.gray11};
border: 1px solid ${COLORS.gray8};
}
.SelectTrigger:hover {
background-color: ${COLORS.rose5};
}
.SelectItem[data-disabled] {
pointer-events: none;
}
.SelectLabel {
padding: 0 25px;
font-size: 12px;
line-height: 25px;
color = ${COLORS.gray8};
}
`;

export const Content = styled.div`
.Scroll {
overflow: scroll;
height: 200px;
background-color: white;
border: 1px solid ${COLORS.rose7};
border-radius: 8px;
box-shadow:
0px 10px 38px -10px rgba(22, 23, 24, 0.35),
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
}
.ScrollViewPort {
width: 100%;
height: 100%;
}
.SelectContent {
}
.SelectScrollButton {
display: flex;
align-items: center;
justify-content: center;
height: 25px;
background-color: white;
color: var(--violet-11);
cursor: default;
}
.SelectItem {
font-size: 13px;
line-height: 1;
color: black;
border-radius: 8px;
display: flex;
align-items: center;
height: 30px;
padding: 0 25px 0 25px;
position: relative;
user-select: none;
color = ${COLORS.gray11};
}
.SelectItem[data-highlighted] {
outline: none;
background-color: ${COLORS.rose5};
cursor: default;
}
.SelectViewport {
padding: 5px;
}
.SelectItemIndicator {
position: absolute;
left: 0;
width: 25px;
display: inline-flex;
align-items: center;
justify-content: center;
}
`;
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
"@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/react": "^6.1.15",
"@next/font": "^14.2.15",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-scroll-area": "^1.2.1",
"@radix-ui/react-select": "^2.1.2",
"@supabase/supabase-js": "^2.45.4",
"classnames": "^2.5.1",
"dotenv": "^16.4.5",
"fullcalendar": "^6.1.15",
"next": "^14.2.10",
Expand Down
Loading

0 comments on commit c33d5a0

Please sign in to comment.