From 2c628181748d3cd437f2da9b4d8776eebf80383e Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 4 Jun 2023 18:51:15 -0700 Subject: [PATCH] cmdk --- src/app/(drift)/(auth)/components/index.tsx | 2 +- .../components/header/post-buttons/index.tsx | 14 +- .../settings/components/sections/api-keys.tsx | 3 +- .../settings/components/sections/profile.tsx | 5 +- src/app/components/cmdk/cmdk.module.css | 165 ------------------ src/app/components/cmdk/cmdk.tsx | 163 +++++++++++++++++ src/app/components/cmdk/dialog.css | 15 +- src/app/components/cmdk/index.tsx | 25 +-- src/app/components/cmdk/item.tsx | 6 +- src/app/components/cmdk/pages/home.tsx | 10 +- src/app/components/dialog.tsx | 128 ++++++++++++++ .../post-list/list-item-skeleton.tsx | 28 +-- src/app/components/settings-group/index.tsx | 4 +- 13 files changed, 337 insertions(+), 231 deletions(-) delete mode 100644 src/app/components/cmdk/cmdk.module.css create mode 100644 src/app/components/cmdk/cmdk.tsx create mode 100644 src/app/components/dialog.tsx diff --git a/src/app/(drift)/(auth)/components/index.tsx b/src/app/(drift)/(auth)/components/index.tsx index 7ba583bf..8f20b013 100644 --- a/src/app/(drift)/(auth)/components/index.tsx +++ b/src/app/(drift)/(auth)/components/index.tsx @@ -123,7 +123,7 @@ function Auth({ width="100%" aria-label="Password" /> - diff --git a/src/app/(drift)/(posts)/post/[id]/components/header/post-buttons/index.tsx b/src/app/(drift)/(posts)/post/[id]/components/header/post-buttons/index.tsx index 672cda9e..cb49693f 100644 --- a/src/app/(drift)/(posts)/post/[id]/components/header/post-buttons/index.tsx +++ b/src/app/(drift)/(posts)/post/[id]/components/header/post-buttons/index.tsx @@ -48,17 +48,9 @@ export const PostButtons = ({ return ( - - {parentId && ( - - )} - + + {parentId && } + diff --git a/src/app/(drift)/settings/components/sections/api-keys.tsx b/src/app/(drift)/settings/components/sections/api-keys.tsx index 9936f902..87ffede0 100644 --- a/src/app/(drift)/settings/components/sections/api-keys.tsx +++ b/src/app/(drift)/settings/components/sections/api-keys.tsx @@ -87,10 +87,9 @@ const APIKeys = ({ diff --git a/src/app/(drift)/settings/components/sections/profile.tsx b/src/app/(drift)/settings/components/sections/profile.tsx index 12a1d530..3fb9f756 100644 --- a/src/app/(drift)/settings/components/sections/profile.tsx +++ b/src/app/(drift)/settings/components/sections/profile.tsx @@ -9,6 +9,7 @@ import { useEffect, useState } from "react" import styles from "./profile.module.css" import useSWR from "swr" import { User } from "@prisma/client" +import { Spinner } from "@components/spinner" function Profile() { const { session } = useSessionSWR() @@ -143,9 +144,7 @@ function Profile() { */} - + ) diff --git a/src/app/components/cmdk/cmdk.module.css b/src/app/components/cmdk/cmdk.module.css deleted file mode 100644 index 4436b9e6..00000000 --- a/src/app/components/cmdk/cmdk.module.css +++ /dev/null @@ -1,165 +0,0 @@ -/** Based on https://github.com/pacocoursey/cmdk **/ -.cmdk[cmdk-root] { - overflow: hidden; - font-family: var(--font-sans); - box-shadow: 0 0 0 1px var(--lighter-gray), 0 4px 16px rgba(0, 0, 0, 0.2); - transition: transform 100ms ease; - border-radius: var(--radius); - - .dark & { - background: rgba(22, 22, 22, 0.7); - } -} - -.cmdk { - /* centered */ - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 999999; - /* size */ - max-width: 640px; - width: 100%; - - [cmdk-list] { - background: var(--bg); - height: 500px; - overflow: auto; - overscroll-behavior: contain; - } - - [cmdk-input] { - font-family: var(--font-sans); - width: 100%; - font-size: 17px; - padding: 8px 8px 16px 8px; - outline: none; - /* background: var(--lightest-gray); */ - color: var(--fg); - - &::placeholder { - color: var(--gray); - } - } - - [cmdk-badge] { - height: 20px; - background: var(--grayA3); - display: inline-flex; - align-items: center; - padding: 0 8px; - font-size: 12px; - color: var(--grayA11); - border-radius: 4px; - margin: 4px 0 4px 4px; - user-select: none; - text-transform: capitalize; - font-weight: 500; - } - - [cmdk-item] { - content-visibility: auto; - - cursor: pointer; - height: 48px; - border-radius: 8px; - font-size: 14px; - display: flex; - align-items: center; - gap: 8px; - padding: 0 16px; - color: var(--darker-gray); - user-select: none; - will-change: background, color; - transition: all 150ms ease; - transition-property: none; - background: var(--bg); - - &[aria-selected="true"] { - background: var(--lightest-gray); - color: var(--fg); - } - - &[aria-disabled="true"] { - /* TODO: improve this */ - color: var(--bg); - cursor: not-allowed; - } - - &:active { - transition-property: background; - background: var(--bg); - } - - & + [cmdk-item] { - margin-top: 4px; - } - - svg { - width: 18px; - height: 18px; - } - } - - [cmdk-list] { - max-height: 400px; - overflow: auto; - overscroll-behavior: contain; - transition: 100ms ease; - transition-property: height; - } - - [cmdk-shortcuts] { - display: flex; - margin-left: auto; - gap: 8px; - - kbd { - font-family: var(--font-sans); - font-size: 12px; - min-width: 20px; - padding: var(--gap-half); - height: 20px; - border-radius: 4px; - color: var(--fg); - background: var(--light-gray); - display: inline-flex; - align-items: center; - justify-content: center; - text-transform: uppercase; - } - } - - [cmdk-separator] { - height: 1px; - width: 100%; - background: var(--light-gray); - margin: 4px 0; - } - - *:not([hidden]) + [cmdk-group] { - margin-top: var(--gap); - } - - [cmdk-group-heading] { - user-select: none; - font-size: 12px; - color: var(--gray); - padding: 0 var(--gap); - display: flex; - align-items: center; - margin-bottom: var(--gap); - margin-top: var(--gap); - } - - [cmdk-empty] { - font-size: 14px; - display: flex; - align-items: center; - justify-content: center; - height: 48px; - white-space: pre-wrap; - color: var(--gray); - } -} diff --git a/src/app/components/cmdk/cmdk.tsx b/src/app/components/cmdk/cmdk.tsx new file mode 100644 index 00000000..88b46242 --- /dev/null +++ b/src/app/components/cmdk/cmdk.tsx @@ -0,0 +1,163 @@ +"use client" + +import * as React from "react" +import { DialogProps } from "@radix-ui/react-dialog" +import { Command as CommandPrimitive } from "cmdk" +import { Search } from "react-feather" + +import { cn } from "@lib/cn" +import { Dialog, DialogContent } from "@components/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +type CommandDialogProps = DialogProps + +const CommandDialog = React.forwardRef< + React.ElementRef, + CommandDialogProps +>(({ children, ...props }, ref) => { + return ( + + + + {children} + + + + ) +}) + +CommandDialog.displayName = Dialog.displayName + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator +} diff --git a/src/app/components/cmdk/dialog.css b/src/app/components/cmdk/dialog.css index 5e75a6dc..d52dfa17 100644 --- a/src/app/components/cmdk/dialog.css +++ b/src/app/components/cmdk/dialog.css @@ -1,16 +1,3 @@ body [cmdk-dialog] { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 999999; - display: flex; - align-items: center; - justify-content: center; - background: rgba(0, 0, 0, 0.5); - /* backdrop-filter: blur(4px); */ - transition: opacity 100ms ease; - pointer-events: none; - will-change: opacity; + } diff --git a/src/app/components/cmdk/index.tsx b/src/app/components/cmdk/index.tsx index d3aa1d22..d512940a 100644 --- a/src/app/components/cmdk/index.tsx +++ b/src/app/components/cmdk/index.tsx @@ -1,8 +1,12 @@ "use client" -import { Command } from "cmdk" +import { + CommandDialog, + CommandList, + CommandInput, + CommandEmpty +} from "./cmdk" import { useEffect, useRef, useState } from "react" -import styles from "./cmdk.module.css" import "./dialog.css" import HomePage from "./pages/home" import PostsPage from "./pages/posts" @@ -10,7 +14,7 @@ import PostsPage from "./pages/posts" export type CmdKPage = "home" | "posts" export default function CmdK() { const [open, setOpen] = useState(false) - const ref = useRef(null) + const ref = useRef() const [page, setPage] = useState("home") // Toggle the menu when ⌘K is pressed @@ -53,21 +57,18 @@ export default function CmdK() { }, [page]) return ( - - - No results found. + + No results found. {page === "home" ? ( ) : null} {page === "posts" ? : null} - - - + + + ) } diff --git a/src/app/components/cmdk/item.tsx b/src/app/components/cmdk/item.tsx index ca337a88..569b3dcb 100644 --- a/src/app/components/cmdk/item.tsx +++ b/src/app/components/cmdk/item.tsx @@ -1,4 +1,4 @@ -import { Command } from "cmdk" +import { CommandItem } from "@components/cmdk/cmdk" export default function Item({ children, @@ -12,7 +12,7 @@ export default function Item({ icon: React.ReactNode }): JSX.Element { return ( - + {icon} {children} {shortcut ? ( @@ -22,6 +22,6 @@ export default function Item({ })} ) : null} - + ) } diff --git a/src/app/components/cmdk/pages/home.tsx b/src/app/components/cmdk/pages/home.tsx index d124c34b..94c4fe19 100644 --- a/src/app/components/cmdk/pages/home.tsx +++ b/src/app/components/cmdk/pages/home.tsx @@ -1,9 +1,9 @@ -import { Command } from "cmdk" import { useTheme } from "next-themes" import { useRouter } from "next/navigation" import { FilePlus, Moon, Search, Settings, Sun } from "react-feather" import { CmdKPage } from ".." import Item from "../item" +import { CommandGroup } from "@components/cmdk/cmdk" export default function HomePage({ setOpen, @@ -16,7 +16,7 @@ export default function HomePage({ const { setTheme, resolvedTheme } = useTheme() return ( <> - + { @@ -36,8 +36,8 @@ export default function HomePage({ > New Post - - + + { @@ -57,7 +57,7 @@ export default function HomePage({ > Go to Settings - + ) } diff --git a/src/app/components/dialog.tsx b/src/app/components/dialog.tsx new file mode 100644 index 00000000..f1e760be --- /dev/null +++ b/src/app/components/dialog.tsx @@ -0,0 +1,128 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "react-feather" + +import { cn } from "@lib/cn" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = ({ + className, + children, + ...props +}: DialogPrimitive.DialogPortalProps) => ( + +
+ {children} +
+
+) +DialogPortal.displayName = DialogPrimitive.Portal.displayName + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription +} diff --git a/src/app/components/post-list/list-item-skeleton.tsx b/src/app/components/post-list/list-item-skeleton.tsx index 9a29a086..52203ff6 100644 --- a/src/app/components/post-list/list-item-skeleton.tsx +++ b/src/app/components/post-list/list-item-skeleton.tsx @@ -1,24 +1,28 @@ import styles from "./list-item.module.css" -import { Card } from "@components/card" +import { Card, CardContent, CardHeader } from "@components/card" import Skeleton from "@components/skeleton" export const ListItemSkeleton = () => (
  • {/* TODO: this is a bad way to do skeletons and is only accurate on desktop */} -
    -
    - {/* title */} - -
    + +
    +
    + {/* title */} + +
    -
    - - - +
    + + + +
    -
    - +
    + + +
  • ) diff --git a/src/app/components/settings-group/index.tsx b/src/app/components/settings-group/index.tsx index ca4dbebe..0b0e3f3b 100644 --- a/src/app/components/settings-group/index.tsx +++ b/src/app/components/settings-group/index.tsx @@ -27,9 +27,7 @@ const SettingsGroup = ({ title, children, skeleton }: Props) => { return ( - -

    {title}

    -
    + {title}