diff --git a/packages/app/app/layout.tsx b/packages/app/app/layout.tsx index 4a8efb20..a17b394f 100644 --- a/packages/app/app/layout.tsx +++ b/packages/app/app/layout.tsx @@ -10,7 +10,13 @@ export const metadata: Metadata = { metadataBase: new URL(process.env.STACKLY_URL ?? defaultStacklyUrl), title: "Stackly | Stack crypto over time.", description: - "Stackly is a simple, non-custodial tool that uses the CoW protocol to place recurring swaps based on DCA.." + "Stackly is a simple, non-custodial tool that uses the CoW protocol to place recurring swaps based on DCA.", + viewport: { + width: "device-width", + initialScale: 1, + maximumScale: 1, + userScalable: false, + }, }; export default function RootLayout({ children }: PropsWithChildren) { diff --git a/packages/app/app/ui/page.tsx b/packages/app/app/ui/page.tsx index b3c39e42..3a358a79 100644 --- a/packages/app/app/ui/page.tsx +++ b/packages/app/app/ui/page.tsx @@ -22,9 +22,11 @@ import { HeadingText, OverlineText, DialogContent, + CaptionText, + Calendar, } from "@/ui"; -import { CaptionText } from "@/ui/text/CaptionText"; import { useRef, useState } from "react"; +import { DatePicker } from "../../components/DatePicker"; export default function Page() { // radioButtons diff --git a/packages/app/components/DatePicker.tsx b/packages/app/components/DatePicker.tsx new file mode 100644 index 00000000..dc6e9942 --- /dev/null +++ b/packages/app/components/DatePicker.tsx @@ -0,0 +1,170 @@ +"use client"; + +import { format, isToday, isTomorrow } from "date-fns"; + +import { Button, Calendar, Icon } from "@/ui"; +import { Popover } from "@headlessui/react"; + +import { Dispatch, Ref, SetStateAction, useEffect, useState } from "react"; +import { usePopper } from "react-popper"; +import { twMerge } from "tailwind-merge"; + +interface DatePickerProps { + dateTime: Date; + setDateTime: Dispatch>; + className?: string; + timeCaption: string; + fromDate?: Date; +} + +export function DatePicker({ + dateTime, + setDateTime, + className, + timeCaption, + fromDate, +}: DatePickerProps) { + const [currentDate, setCurrentDate] = useState(new Date(dateTime)); + const [hours, setHours] = useState(format(currentDate, "HH")); + const [minutes, setMinutes] = useState(format(currentDate, "mm")); + + const [referenceElement, setReferenceElement] = useState< + HTMLButtonElement | undefined + >(); + const [popperElement, setPopperElement] = useState< + HTMLDivElement | undefined + >(); + + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: "bottom", + }); + + const formattedDate = () => { + if (isToday(dateTime)) return format(dateTime, "'Today at' HH:mm"); + if (isTomorrow(dateTime)) return format(dateTime, "'Tomorrow at' HH:mm"); + + return format(dateTime, "dd MMM Y 'at' HH:mm"); + }; + + useEffect(() => { + const newDate = new Date(dateTime); + setCurrentDate(newDate); + setHours(format(newDate, "HH")); + setMinutes(format(newDate, "mm")); + }, [dateTime]); + + return ( + + {({ open }) => ( + <> + } + className={twMerge( + "flex justify-between items-center focus:border-0", + className + )} + > + {formattedDate()} + + + {open && ( + <> + -
+
Stack WETH every -
- {}} - > - Hour - - {}} - > - Week - - {}} - > - Month - +
+
+ {frequencyOptions.map(({ option, name }) => { + const isSelected = frequency === option; + return ( + setFrequency(event.target.value)} + > + + {name} + + + ); + })} +
+
+
+ Starting from + +
+
+ Until + +
+
-

The stackbox™

diff --git a/packages/app/components/token-picker/index.ts b/packages/app/components/token-picker/index.ts new file mode 100644 index 00000000..51a4d7a9 --- /dev/null +++ b/packages/app/components/token-picker/index.ts @@ -0,0 +1 @@ +export * from "./TokenPicker"; diff --git a/packages/app/package.json b/packages/app/package.json index 576fe42f..97a34d80 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -16,11 +16,15 @@ "@cowprotocol/cow-sdk": "^2.1.0", "@headlessui/react": "^1.7.14", "@headlessui/tailwindcss": "^0.1.3", + "@popperjs/core": "^2.11.8", "class-variance-authority": "^0.6.0", "connectkit": "^1.4.0", + "date-fns": "^2.30.0", "next": "13.4.10", "react": "^18.2.0", + "react-day-picker": "^8.8.0", "react-dom": "^18.2.0", + "react-popper": "^2.3.0", "tailwind-merge": "^1.12.0", "viem": "^1.0.2", "wagmi": "^1.1.1" diff --git a/packages/app/styles/global.css b/packages/app/styles/global.css index a27072b3..b34bb708 100644 --- a/packages/app/styles/global.css +++ b/packages/app/styles/global.css @@ -19,3 +19,11 @@ @tailwind components; @tailwind utilities; + +@layer base { + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } +} diff --git a/packages/app/ui/Calendar.tsx b/packages/app/ui/Calendar.tsx new file mode 100644 index 00000000..067bfc3c --- /dev/null +++ b/packages/app/ui/Calendar.tsx @@ -0,0 +1,66 @@ +"use client"; + +import * as React from "react"; + +import { DayPicker } from "react-day-picker"; + +import { buttonStyles, Icon } from "."; +import { twMerge } from "tailwind-merge"; + +export type CalendarProps = React.ComponentProps; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: CalendarProps) { + return ( + ( + + ), + IconRight: ({ ...props }) => ( + + ), + }} + {...props} + /> + ); +} +Calendar.displayName = "Calendar"; + +export { Calendar }; diff --git a/packages/app/ui/buttons/index.ts b/packages/app/ui/buttons/index.ts index b5fb6be9..fc01efb9 100644 --- a/packages/app/ui/buttons/index.ts +++ b/packages/app/ui/buttons/index.ts @@ -2,3 +2,4 @@ export * from "./Button"; export * from "./ButtonLink"; export * from "./ChipButton"; export * from "./RadioButton"; +export * from "./base"; diff --git a/packages/app/ui/index.ts b/packages/app/ui/index.ts index 8f18fe8e..354c8443 100644 --- a/packages/app/ui/index.ts +++ b/packages/app/ui/index.ts @@ -4,3 +4,4 @@ export * from "./icon/Icon"; export * from "./modal"; export * from "./table/Table"; export * from "./text"; +export * from "./Calendar"; diff --git a/yarn.lock b/yarn.lock index 65774661..3aff32e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1425,7 +1425,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.8.4": +"@babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.8.4": version: 7.22.6 resolution: "@babel/runtime@npm:7.22.6" dependencies: @@ -2685,6 +2685,13 @@ __metadata: languageName: node linkType: hard +"@popperjs/core@npm:^2.11.8": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: e5c69fdebf52a4012f6a1f14817ca8e9599cb1be73dd1387e1785e2ed5e5f0862ff817f420a87c7fc532add1f88a12e25aeb010ffcbdc98eace3d55ce2139cf0 + languageName: node + linkType: hard + "@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": version: 1.1.2 resolution: "@protobufjs/aspromise@npm:1.1.2" @@ -4532,18 +4539,22 @@ __metadata: "@cowprotocol/cow-sdk": ^2.1.0 "@headlessui/react": ^1.7.14 "@headlessui/tailwindcss": ^0.1.3 + "@popperjs/core": ^2.11.8 "@svgr/webpack": ^8.0.1 "@types/node": 20.3.1 "@types/react": 18.2.13 autoprefixer: ^10.4.14 class-variance-authority: ^0.6.0 connectkit: ^1.4.0 + date-fns: ^2.30.0 eslint: ^8.11.0 eslint-config-next: canary next: 13.4.10 postcss: ^8.4.23 react: ^18.2.0 + react-day-picker: ^8.8.0 react-dom: ^18.2.0 + react-popper: ^2.3.0 sharp: ^0.32.1 tailwind-merge: ^1.12.0 tailwindcss: ^3.3.2 @@ -5972,6 +5983,15 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^2.30.0": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": ^7.21.0 + checksum: f7be01523282e9bb06c0cd2693d34f245247a29098527d4420628966a2d9aad154bd0e90a6b1cf66d37adcb769cd108cf8a7bd49d76db0fb119af5cdd13644f4 + languageName: node + linkType: hard + "debounce@npm:^1.2.1": version: 1.2.1 resolution: "debounce@npm:1.2.1" @@ -9502,7 +9522,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -11188,6 +11208,16 @@ __metadata: languageName: node linkType: hard +"react-day-picker@npm:^8.8.0": + version: 8.8.0 + resolution: "react-day-picker@npm:8.8.0" + peerDependencies: + date-fns: ^2.28.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: c104b4ae44bad44183f6a273327aa1d00d23afe6f3860f8335eea2d06c0f64ea015ea7438655c79a5024a8db259ba5fa9431dbf92f03889120d0c2c00a28117f + languageName: node + linkType: hard + "react-dom@npm:^18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0" @@ -11200,6 +11230,13 @@ __metadata: languageName: node linkType: hard +"react-fast-compare@npm:^3.0.1": + version: 3.2.2 + resolution: "react-fast-compare@npm:3.2.2" + checksum: 2071415b4f76a3e6b55c84611c4d24dcb12ffc85811a2840b5a3f1ff2d1a99be1020d9437ee7c6e024c9f4cbb84ceb35e48cf84f28fcb00265ad2dfdd3947704 + languageName: node + linkType: hard + "react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -11216,6 +11253,20 @@ __metadata: languageName: node linkType: hard +"react-popper@npm:^2.3.0": + version: 2.3.0 + resolution: "react-popper@npm:2.3.0" + dependencies: + react-fast-compare: ^3.0.1 + warning: ^4.0.2 + peerDependencies: + "@popperjs/core": ^2.0.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + checksum: 837111c98738011c69b3069a464ea5bdcbf487105b6148e8faf90cb7337e134edb1b98b8824322941c378756cca30a15c18c25f558e53b85ed5762fa0dc8e6b2 + languageName: node + linkType: hard + "react-transition-state@npm:^1.1.4": version: 1.1.5 resolution: "react-transition-state@npm:1.1.5" @@ -13396,6 +13447,15 @@ __metadata: languageName: node linkType: hard +"warning@npm:^4.0.2": + version: 4.0.3 + resolution: "warning@npm:4.0.3" + dependencies: + loose-envify: ^1.0.0 + checksum: 4f2cb6a9575e4faf71ddad9ad1ae7a00d0a75d24521c193fa464f30e6b04027bd97aa5d9546b0e13d3a150ab402eda216d59c1d0f2d6ca60124d96cd40dfa35c + languageName: node + linkType: hard + "watchpack@npm:2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0"