mirror of https://github.com/MaxLeiter/Drift
parent
702f59caf8
commit
5d5fd3182e
@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@components",
|
||||
"utils": "@utils"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
"postcss-nested",
|
||||
"postcss-flexbugs-fixes",
|
||||
"postcss-hover-media-feature",
|
||||
[
|
||||
"postcss-preset-env",
|
||||
{
|
||||
stage: 3,
|
||||
features: {
|
||||
"custom-media-queries": true,
|
||||
"custom-properties": false
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
plugins: {
|
||||
"@tailwindcss/nesting": {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.list .formattingIcons {
|
||||
margin-left: auto;
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.list .formattingIcons {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
margin: 4px 0;
|
||||
font-family: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.1s ease;
|
||||
margin-bottom: var(--gap-half);
|
||||
}
|
||||
|
||||
.trigger:hover {
|
||||
background-color: var(--lighter-gray);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.trigger:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
|
||||
.trigger:last-child {
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.trigger[data-state="active"] {
|
||||
color: var(--darkest-gray);
|
||||
box-shadow: inset 0 -1px 0 0 currentColor, 0 1px 0 0 currentColor;
|
||||
}
|
@ -1,372 +0,0 @@
|
||||
.react-datepicker__year-read-view--down-arrow,
|
||||
.react-datepicker__month-read-view--down-arrow,
|
||||
.react-datepicker__month-year-read-view--down-arrow,
|
||||
.react-datepicker__navigation-icon::before {
|
||||
border-color: var(--light-gray);
|
||||
border-style: solid;
|
||||
border-width: 3px 3px 0 0;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 9px;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
width: 9px;
|
||||
}
|
||||
.react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle,
|
||||
.react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle {
|
||||
margin-left: -4px;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.react-datepicker-wrapper {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.react-datepicker {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.8rem;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--gray);
|
||||
border-radius: var(--radius);
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-datepicker--time-only .react-datepicker__triangle {
|
||||
left: 35px;
|
||||
}
|
||||
.react-datepicker--time-only .react-datepicker__time-container {
|
||||
border-left: 0;
|
||||
}
|
||||
.react-datepicker--time-only .react-datepicker__time,
|
||||
.react-datepicker--time-only .react-datepicker__time-box {
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.react-datepicker__triangle {
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
.react-datepicker-popper {
|
||||
z-index: 1;
|
||||
}
|
||||
.react-datepicker-popper[data-placement^="bottom"] {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.react-datepicker-popper[data-placement="bottom-end"]
|
||||
.react-datepicker__triangle,
|
||||
.react-datepicker-popper[data-placement="top-end"] .react-datepicker__triangle {
|
||||
left: auto;
|
||||
right: 50px;
|
||||
}
|
||||
.react-datepicker-popper[data-placement^="top"] {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.react-datepicker-popper[data-placement^="right"] {
|
||||
padding-left: 8px;
|
||||
}
|
||||
.react-datepicker-popper[data-placement^="right"] .react-datepicker__triangle {
|
||||
left: auto;
|
||||
right: 42px;
|
||||
}
|
||||
.react-datepicker-popper[data-placement^="left"] {
|
||||
padding-right: 8px;
|
||||
}
|
||||
.react-datepicker-popper[data-placement^="left"] .react-datepicker__triangle {
|
||||
left: 42px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.react-datepicker__header {
|
||||
text-align: center;
|
||||
background-color: var(--bg);
|
||||
border-bottom: 1px solid var(--gray);
|
||||
border-top-left-radius: var(--radius);
|
||||
border-top-right-radius: var(--radius);
|
||||
padding: 8px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-datepicker__header--time {
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.react-datepicker__year-dropdown-container--select,
|
||||
.react-datepicker__month-dropdown-container--select,
|
||||
.react-datepicker__month-year-dropdown-container--select,
|
||||
.react-datepicker__year-dropdown-container--scroll,
|
||||
.react-datepicker__month-dropdown-container--scroll,
|
||||
.react-datepicker__month-year-dropdown-container--scroll {
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.react-datepicker__current-month,
|
||||
.react-datepicker-time__header,
|
||||
.react-datepicker-year-header {
|
||||
margin-top: 0;
|
||||
font-weight: bold;
|
||||
font-size: 0.944rem;
|
||||
}
|
||||
|
||||
.react-datepicker-time__header {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation {
|
||||
align-items: center;
|
||||
background: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
z-index: 1;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
text-indent: -999em;
|
||||
overflow: hidden;
|
||||
}
|
||||
.react-datepicker__navigation--previous {
|
||||
left: 2px;
|
||||
}
|
||||
.react-datepicker__navigation--next {
|
||||
right: 2px;
|
||||
}
|
||||
.react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
|
||||
right: 85px;
|
||||
}
|
||||
.react-datepicker__navigation--years {
|
||||
position: relative;
|
||||
top: 0;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.react-datepicker__navigation--years-previous {
|
||||
top: 4px;
|
||||
}
|
||||
.react-datepicker__navigation--years-upcoming {
|
||||
top: -4px;
|
||||
}
|
||||
.react-datepicker__navigation:hover *::before {
|
||||
border-color: var(--lighter-gray);
|
||||
}
|
||||
|
||||
.react-datepicker__navigation-icon {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
font-size: 20px;
|
||||
width: 0;
|
||||
}
|
||||
.react-datepicker__navigation-icon--next {
|
||||
left: -2px;
|
||||
}
|
||||
.react-datepicker__navigation-icon--next::before {
|
||||
transform: rotate(45deg);
|
||||
left: -7px;
|
||||
}
|
||||
.react-datepicker__navigation-icon--previous {
|
||||
right: -2px;
|
||||
}
|
||||
.react-datepicker__navigation-icon--previous::before {
|
||||
transform: rotate(225deg);
|
||||
right: -7px;
|
||||
}
|
||||
|
||||
.react-datepicker__month-container {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.react-datepicker__year {
|
||||
margin: 0.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
.react-datepicker__year-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
max-width: 180px;
|
||||
}
|
||||
.react-datepicker__year .react-datepicker__year-text {
|
||||
display: inline-block;
|
||||
width: 4rem;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.react-datepicker__month {
|
||||
margin: 0.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
.react-datepicker__month .react-datepicker__month-text,
|
||||
.react-datepicker__month .react-datepicker__quarter-text {
|
||||
display: inline-block;
|
||||
width: 4rem;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.react-datepicker__input-time-container {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
float: left;
|
||||
margin: 5px 0 10px 15px;
|
||||
text-align: left;
|
||||
}
|
||||
.react-datepicker__input-time-container .react-datepicker-time__caption {
|
||||
display: inline-block;
|
||||
}
|
||||
.react-datepicker__input-time-container
|
||||
.react-datepicker-time__input-container {
|
||||
display: inline-block;
|
||||
}
|
||||
.react-datepicker__input-time-container
|
||||
.react-datepicker-time__input-container
|
||||
.react-datepicker-time__input {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.react-datepicker__input-time-container
|
||||
.react-datepicker-time__input-container
|
||||
.react-datepicker-time__input
|
||||
input {
|
||||
width: auto;
|
||||
}
|
||||
.react-datepicker__input-time-container
|
||||
.react-datepicker-time__input-container
|
||||
.react-datepicker-time__input
|
||||
input[type="time"]::-webkit-inner-spin-button,
|
||||
.react-datepicker__input-time-container
|
||||
.react-datepicker-time__input-container
|
||||
.react-datepicker-time__input
|
||||
input[type="time"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
.react-datepicker__input-time-container
|
||||
.react-datepicker-time__input-container
|
||||
.react-datepicker-time__input
|
||||
input[type="time"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
.react-datepicker__input-time-container
|
||||
.react-datepicker-time__input-container
|
||||
.react-datepicker-time__delimiter {
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.react-datepicker__day-names,
|
||||
.react-datepicker__week {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.react-datepicker__day-names {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
|
||||
.react-datepicker__day-name,
|
||||
.react-datepicker__day,
|
||||
.react-datepicker__time-name {
|
||||
color: var(--fg);
|
||||
display: inline-block;
|
||||
width: 1.7rem;
|
||||
line-height: 1.7rem;
|
||||
text-align: center;
|
||||
margin: 0.166rem;
|
||||
}
|
||||
.react-datepicker__day,
|
||||
.react-datepicker__month-text,
|
||||
.react-datepicker__quarter-text,
|
||||
.react-datepicker__year-text {
|
||||
cursor: pointer;
|
||||
}
|
||||
.react-datepicker__day:hover,
|
||||
.react-datepicker__month-text:hover,
|
||||
.react-datepicker__quarter-text:hover,
|
||||
.react-datepicker__year-text:hover {
|
||||
border-radius: 0.3rem;
|
||||
background-color: var(--light-gray);
|
||||
}
|
||||
.react-datepicker__day--today,
|
||||
.react-datepicker__month-text--today,
|
||||
.react-datepicker__quarter-text--today,
|
||||
.react-datepicker__year-text--today {
|
||||
font-weight: bold;
|
||||
}
|
||||
.react-datepicker__day--highlighted,
|
||||
.react-datepicker__month-text--highlighted,
|
||||
.react-datepicker__quarter-text--highlighted,
|
||||
.react-datepicker__year-text--highlighted {
|
||||
border-radius: 0.3rem;
|
||||
background-color: #3dcc4a;
|
||||
color: var(--fg);
|
||||
}
|
||||
.react-datepicker__day--highlighted:hover,
|
||||
.react-datepicker__month-text--highlighted:hover,
|
||||
.react-datepicker__quarter-text--highlighted:hover,
|
||||
.react-datepicker__year-text--highlighted:hover {
|
||||
background-color: #32be3f;
|
||||
}
|
||||
|
||||
.react-datepicker__day--selected,
|
||||
.react-datepicker__day--in-selecting-range,
|
||||
.react-datepicker__day--in-range,
|
||||
.react-datepicker__month-text--selected,
|
||||
.react-datepicker__month-text--in-selecting-range,
|
||||
.react-datepicker__month-text--in-range,
|
||||
.react-datepicker__quarter-text--selected,
|
||||
.react-datepicker__quarter-text--in-selecting-range,
|
||||
.react-datepicker__quarter-text--in-range,
|
||||
.react-datepicker__year-text--selected,
|
||||
.react-datepicker__year-text--in-selecting-range,
|
||||
.react-datepicker__year-text--in-range {
|
||||
border-radius: 0.3rem;
|
||||
background-color: var(--light-gray);
|
||||
color: var(--fg);
|
||||
}
|
||||
.react-datepicker__day--selected:hover {
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
.react-datepicker__day--keyboard-selected,
|
||||
.react-datepicker__month-text--keyboard-selected,
|
||||
.react-datepicker__quarter-text--keyboard-selected,
|
||||
.react-datepicker__year-text--keyboard-selected {
|
||||
border-radius: 0.3rem;
|
||||
background-color: var(--light-gray);
|
||||
color: var(--fg);
|
||||
}
|
||||
.react-datepicker__day--keyboard-selected:hover {
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
.react-datepicker__month--selecting-range
|
||||
.react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range, .react-datepicker__month-text--in-selecting-range, .react-datepicker__quarter-text--in-selecting-range, .react-datepicker__year-text--in-selecting-range) {
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.react-datepicker {
|
||||
transform: scale(1.15) translateY(-12px);
|
||||
}
|
||||
|
||||
.react-datepicker__day--disabled {
|
||||
color: var(--darker-gray);
|
||||
}
|
||||
|
||||
.react-datepicker__day--disabled:hover {
|
||||
background-color: transparent;
|
||||
cursor: not-allowed;
|
||||
}
|
@ -1,7 +1,15 @@
|
||||
"use client"
|
||||
|
||||
import { PageTitle } from "@components/page-title"
|
||||
import { PageWrapper } from "@components/page-wrapper"
|
||||
import PostList from "@components/post-list"
|
||||
|
||||
export default function Loading() {
|
||||
return <PostList skeleton={true} initialPosts={[]} />
|
||||
return (
|
||||
<>
|
||||
<PageTitle>Your Posts</PageTitle>
|
||||
<PageWrapper></PageWrapper>
|
||||
<PostList skeleton={true} initialPosts={[]} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
export default function SettingsLayout({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<h1>Settings</h1>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "var(--gap)",
|
||||
marginBottom: "var(--gap)",
|
||||
marginTop: "var(--gap)"
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,5 +1,15 @@
|
||||
import { PageTitle } from "@components/page-title"
|
||||
import { PageWrapper } from "@components/page-wrapper"
|
||||
import SettingsGroup from "@components/settings-group"
|
||||
|
||||
export default function SettingsLoading() {
|
||||
return <SettingsGroup skeleton />
|
||||
return (
|
||||
<>
|
||||
<PageTitle>Settings</PageTitle>
|
||||
<PageWrapper>
|
||||
<SettingsGroup skeleton />
|
||||
<SettingsGroup skeleton />
|
||||
</PageWrapper>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
import { authOptions } from "@lib/server/auth"
|
||||
import NextAuth from "next-auth"
|
||||
|
||||
const handler = NextAuth(authOptions)
|
||||
|
||||
export { handler as GET, handler as POST }
|
@ -0,0 +1,41 @@
|
||||
import config from "@lib/config"
|
||||
import { NextRequest } from "next/server"
|
||||
|
||||
export const getRequiresPasscode = async () => {
|
||||
const requiresPasscode = Boolean(config.registration_password)
|
||||
return requiresPasscode
|
||||
}
|
||||
|
||||
export default async function GET(req: NextRequest) {
|
||||
const searchParams = new URL(req.nextUrl).searchParams
|
||||
const slug = searchParams.get("slug")
|
||||
|
||||
if (!slug || Array.isArray(slug)) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
statusText: "Bad request"
|
||||
})
|
||||
}
|
||||
|
||||
if (slug === "requires-passcode") {
|
||||
// return res.json({ requiresPasscode: await getRequiresPasscode() })
|
||||
return new Response(
|
||||
JSON.stringify({ requiresPasscode: await getRequiresPasscode() }),
|
||||
{
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ error: "Not found" }), {
|
||||
status: 404,
|
||||
statusText: "Not found",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
|
||||
import { cn } from "@lib/cn"
|
||||
import { buttonVariants } from "@components/button"
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root
|
||||
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||
|
||||
const AlertDialogPortal = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
|
||||
<AlertDialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
</AlertDialogPrimitive.Portal>
|
||||
)
|
||||
AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName
|
||||
|
||||
const AlertDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-opacity animate-in fade-in",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||
|
||||
const AlertDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed z-50 grid w-full max-w-lg scale-100 gap-4 border bg-background p-6 opacity-100 shadow-lg animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
))
|
||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||
|
||||
const AlertDialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||
|
||||
const AlertDialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogFooter.displayName = "AlertDialogFooter"
|
||||
|
||||
const AlertDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||
|
||||
const AlertDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogDescription.displayName =
|
||||
AlertDialogPrimitive.Description.displayName
|
||||
|
||||
const AlertDialogAction = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Action
|
||||
ref={ref}
|
||||
className={cn(buttonVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||
|
||||
const AlertDialogCancel = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"mt-2 sm:mt-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel
|
||||
}
|
@ -1,21 +1,35 @@
|
||||
import React from "react"
|
||||
import styles from "./badge.module.css"
|
||||
type BadgeProps = {
|
||||
type: "primary" | "secondary" | "error" | "warning"
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "@lib/cn"
|
||||
|
||||
const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
|
||||
({ type, children, ...rest }: BadgeProps, ref) => {
|
||||
return (
|
||||
<div className={styles.container} {...rest}>
|
||||
<div className={`${styles.badge} ${styles[type]}`} ref={ref}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center border rounded-full font-medium px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary hover:bg-primary/80 border-transparent text-primary-foreground",
|
||||
secondary:
|
||||
"bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground",
|
||||
destructive:
|
||||
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
|
||||
outline: "text-foreground"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Badge.displayName = "Badge"
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
export default Badge
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Badge from "../badge"
|
||||
import { Badge } from "../badge"
|
||||
|
||||
type Props = {
|
||||
visibility: string
|
||||
}
|
||||
|
||||
const VisibilityBadge = ({ visibility }: Props) => {
|
||||
return <Badge type={"primary"}>{visibility}</Badge>
|
||||
return <Badge variant={"outline"}>{visibility}</Badge>
|
||||
}
|
||||
|
||||
export default VisibilityBadge
|
||||
|
@ -1,56 +0,0 @@
|
||||
.button {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--darker-gray);
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
cursor: not-allowed;
|
||||
background: var(--lighter-gray);
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.button[disabled]:hover,
|
||||
.button[disabled]:focus {
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: var(--fg);
|
||||
color: var(--bg);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.iconRight {
|
||||
margin-left: var(--gap-half);
|
||||
}
|
||||
|
||||
.iconLeft {
|
||||
margin-right: var(--gap-half);
|
||||
}
|
||||
|
||||
.icon svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: scale(1.2) translateY(-0.05em);
|
||||
}
|
@ -1,86 +1,65 @@
|
||||
import styles from "./button.module.css"
|
||||
import { forwardRef } from "react"
|
||||
import clsx from "clsx"
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@lib/cn"
|
||||
import { Spinner } from "@components/spinner"
|
||||
|
||||
type Props = React.DetailedHTMLProps<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
> & {
|
||||
children?: React.ReactNode
|
||||
buttonType?: "primary" | "secondary"
|
||||
className?: string
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
iconRight?: React.ReactNode
|
||||
iconLeft?: React.ReactNode
|
||||
height?: string | number
|
||||
width?: string | number
|
||||
padding?: string | number
|
||||
margin?: string | number
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-75 disabled:pointer-events-none ring-offset-background",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary/80 text-primary-foreground/80 hover:bg-primary/70",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "underline-offset-4 hover:underline text-primary"
|
||||
},
|
||||
size: {
|
||||
default: "h-10 py-2 px-4",
|
||||
sm: "h-9 px-3 rounded-md",
|
||||
lg: "h-11 px-8 rounded-md"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const Button = forwardRef<HTMLButtonElement, Props>(
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
onClick,
|
||||
className,
|
||||
buttonType = "secondary",
|
||||
disabled = false,
|
||||
iconRight,
|
||||
iconLeft,
|
||||
height = 40,
|
||||
width,
|
||||
padding = 10,
|
||||
margin,
|
||||
loading,
|
||||
style,
|
||||
...props
|
||||
},
|
||||
{ className, variant, size, loading, children, asChild = false, ...props },
|
||||
ref
|
||||
) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<button
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
className={clsx(styles.button, className, {
|
||||
[styles.primary]: buttonType === "primary",
|
||||
[styles.secondary]: buttonType === "secondary"
|
||||
})}
|
||||
disabled={disabled || loading}
|
||||
onClick={onClick}
|
||||
style={{ height, width, margin, padding, ...style }}
|
||||
{...props}
|
||||
>
|
||||
{children && iconLeft && (
|
||||
<span className={clsx(styles.icon, styles.iconLeft)}>{iconLeft}</span>
|
||||
)}
|
||||
{!loading &&
|
||||
(children ? (
|
||||
children
|
||||
) : (
|
||||
<span className={styles.icon}>{iconLeft || iconRight}</span>
|
||||
))}
|
||||
{loading && (
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Spinner />
|
||||
</span>
|
||||
)}
|
||||
{children && iconRight && (
|
||||
<span className={clsx(styles.icon, styles.iconRight)}>
|
||||
{iconRight}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{loading ? <Spinner className="mr-2" /> : null}
|
||||
{children}
|
||||
</Comp>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default Button
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
|
@ -0,0 +1,64 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronLeft, ChevronRight } from "react-feather"
|
||||
import { DayPicker } from "react-day-picker"
|
||||
|
||||
import { cn } from "@lib/cn"
|
||||
import { buttonVariants } from "@components/button"
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
month: "space-y-4",
|
||||
caption: "flex justify-center pt-1 relative items-center",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "space-x-1 flex items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-y-1",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: "text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
|
||||
),
|
||||
day_selected:
|
||||
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||
day_today: "bg-accent text-accent-foreground",
|
||||
day_outside: "text-muted-foreground opacity-50",
|
||||
day_disabled: "text-muted-foreground opacity-50",
|
||||
day_range_middle:
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||
day_hidden: "invisible",
|
||||
...classNames
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({}) => <ChevronLeft className="h-4 w-4" />,
|
||||
IconRight: ({}) => <ChevronRight className="h-4 w-4" />
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Calendar.displayName = "Calendar"
|
||||
|
||||
export { Calendar }
|
@ -1,16 +1,78 @@
|
||||
import styles from "./card.module.css"
|
||||
import * as React from "react"
|
||||
import { cn } from "@lib/cn"
|
||||
|
||||
export default function Card({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
} & React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div className={`${styles.card} ${className || ""}`} {...props}>
|
||||
<div className={styles.content}>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(" flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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<typeof CommandPrimitive>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Command.displayName = CommandPrimitive.displayName
|
||||
|
||||
type CommandDialogProps = DialogProps
|
||||
|
||||
const CommandDialog = React.forwardRef<
|
||||
React.ElementRef<typeof Dialog>,
|
||||
CommandDialogProps
|
||||
>(({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogContent className="overflow-hidden p-0 shadow-2xl">
|
||||
<Command
|
||||
ref={ref}
|
||||
className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"
|
||||
>
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
})
|
||||
|
||||
CommandDialog.displayName = Dialog.displayName
|
||||
|
||||
const CommandInput = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
||||
CommandInput.displayName = CommandPrimitive.Input.displayName
|
||||
|
||||
const CommandList = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.List
|
||||
ref={ref}
|
||||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
CommandList.displayName = CommandPrimitive.List.displayName
|
||||
|
||||
const CommandEmpty = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||
>((props, ref) => (
|
||||
<CommandPrimitive.Empty
|
||||
ref={ref}
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
||||
|
||||
const CommandGroup = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Group
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
||||
|
||||
const CommandSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
||||
|
||||
const CommandItem = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
CommandItem.displayName = CommandPrimitive.Item.displayName
|
||||
|
||||
const CommandShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
CommandShortcut.displayName = "CommandShortcut"
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { format } from "date-fns"
|
||||
import { Calendar as CalendarIcon } from "react-feather"
|
||||
|
||||
import { cn } from "@lib/cn"
|
||||
import { Button } from "@components/button"
|
||||
import { Calendar } from "@components/calendar"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@components/popover"
|
||||
|
||||
export function DatePicker({
|
||||
expiresAt,
|
||||
setExpiresAt
|
||||
}: {
|
||||
expiresAt?: Date
|
||||
setExpiresAt: React.Dispatch<React.SetStateAction<Date | undefined>>
|
||||
}) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] justify-start text-left font-normal",
|
||||
!expiresAt && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{expiresAt ? (
|
||||
format(expiresAt, "PPP")
|
||||
) : (
|
||||
<span>Won't expire</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={expiresAt}
|
||||
onSelect={(date) => {
|
||||
setExpiresAt(date)
|
||||
}}
|
||||
initialFocus
|
||||
fromDate={new Date()}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
@ -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) => (
|
||||
<DialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
</DialogPrimitive.Portal>
|
||||
)
|
||||
DialogPortal.displayName = DialogPrimitive.Portal.displayName
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { Check, ChevronRight, Circle } from "react-feather"
|
||||
|
||||
import { cn } from "@lib/cn"
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.active {
|
||||
color: var(--fg) !important;
|
||||
}
|
@ -1,16 +1,120 @@
|
||||
import styles from "./header.module.css"
|
||||
import { HeaderButtons } from "./buttons"
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { cn } from "@lib/cn"
|
||||
|
||||
import { useSessionSWR } from "@lib/use-session-swr"
|
||||
import { PropsWithChildren, useEffect, useState } from "react"
|
||||
import { useSelectedLayoutSegments } from "next/navigation"
|
||||
import Image from "next/image"
|
||||
import { useTheme } from "next-themes"
|
||||
import { Moon, Sun } from "react-feather"
|
||||
import FadeIn from "@components/fade-in"
|
||||
import MobileHeader from "./mobile"
|
||||
|
||||
export default function Header() {
|
||||
const { isAdmin, isAuthenticated } = useSessionSWR()
|
||||
const { resolvedTheme, setTheme } = useTheme()
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
const toggleTheme = () => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<div className={styles.tabs}>
|
||||
<div className={styles.buttons}>
|
||||
<HeaderButtons />
|
||||
</div>
|
||||
</div>
|
||||
<MobileHeader />
|
||||
<header className="mt-4 flex h-16 items-center justify-start md:justify-between">
|
||||
<span className="hidden items-center md:flex">
|
||||
<Link href="/" className="mr-4 flex items-center">
|
||||
<Image
|
||||
src={"/assets/logo.svg"}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
priority
|
||||
/>
|
||||
<span className="bg-transparent pl-4 text-lg font-bold">Drift</span>
|
||||
</Link>
|
||||
<nav className="flex space-x-4 lg:space-x-6">
|
||||
<ul className="flex justify-center space-x-4">
|
||||
<NavLink href="/home">Home</NavLink>
|
||||
<NavLink href="/new" disabled={!isAuthenticated}>
|
||||
New
|
||||
</NavLink>
|
||||
<NavLink href="/mine" disabled={!isAuthenticated}>
|
||||
Yours
|
||||
</NavLink>
|
||||
<NavLink href="/settings" disabled={!isAuthenticated}>
|
||||
Settings
|
||||
</NavLink>
|
||||
{isAdmin && <NavLink href="/admin">Admin</NavLink>}
|
||||
{isAuthenticated !== undefined && (
|
||||
<>
|
||||
{isAuthenticated === true && (
|
||||
<NavLink href="/signout">Sign Out</NavLink>
|
||||
)}
|
||||
{isAuthenticated === false && (
|
||||
<NavLink href="/signin">Sign In</NavLink>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
</span>
|
||||
<span className="flex items-center justify-center md:hidden">
|
||||
<MobileHeader />
|
||||
</span>
|
||||
{isMounted && (
|
||||
<FadeIn>
|
||||
<button
|
||||
aria-hidden
|
||||
className="ml-4 flex h-8 w-8 cursor-pointer items-center justify-center font-medium text-muted-foreground transition-colors hover:text-primary md:ml-0"
|
||||
onClick={toggleTheme}
|
||||
title="Toggle theme"
|
||||
>
|
||||
{resolvedTheme === "dark" ? (
|
||||
<Sun className="h-[16px] w-[16px]" />
|
||||
) : (
|
||||
<Moon className="h-[16px] w-[16px]" />
|
||||
)}
|
||||
</button>
|
||||
</FadeIn>
|
||||
)}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
type NavLinkProps = PropsWithChildren<{
|
||||
href: string
|
||||
disabled?: boolean
|
||||
active?: boolean
|
||||
}>
|
||||
|
||||
function NavLink({ href, disabled, children }: NavLinkProps) {
|
||||
const baseClasses =
|
||||
"text-sm text-muted-foreground font-medium transition-colors hover:text-primary"
|
||||
const activeClasses = "text-primary border-primary"
|
||||
const disabledClasses = "text-gray-600 hover:text-gray-400 cursor-not-allowed"
|
||||
|
||||
const segments = useSelectedLayoutSegments()
|
||||
const activeSegment = segments[segments.length - 1]
|
||||
const isActive =
|
||||
activeSegment === href.slice(1) ||
|
||||
// special case / because it's an alias of /home/page.tsx
|
||||
(!activeSegment && href === "/home")
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className={cn(
|
||||
baseClasses,
|
||||
isActive && activeClasses,
|
||||
disabled && disabledClasses
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
.mobileTrigger {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.header {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wrapper [data-tab="github"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobileTrigger {
|
||||
margin-top: var(--gap);
|
||||
margin-bottom: var(--gap);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mobileTrigger button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdownItem a,
|
||||
.dropdownItem button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdownItem:not(:first-child):not(:last-child) :global(button) {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.dropdownItem:first-child :global(button) {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.dropdownItem:last-child :global(button) {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -1,82 +1,51 @@
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import styles from "./input.module.css"
|
||||
import * as React from "react"
|
||||
|
||||
type Props = React.HTMLProps<HTMLInputElement> & {
|
||||
import { cn } from "@lib/cn"
|
||||
|
||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||
label?: string
|
||||
width?: number | string
|
||||
height?: number | string
|
||||
labelClassName?: string
|
||||
hideLabel?: boolean
|
||||
}
|
||||
|
||||
// we have two special rules on top of the props:
|
||||
// if onChange or value is passed, we require both, unless `disabled`
|
||||
// if label is passed, we forbid aria-label and vice versa
|
||||
type InputProps = Omit<Props, "onChange" | "value" | "label" | "aria-label"> &
|
||||
(
|
||||
| {
|
||||
onChange: Props["onChange"]
|
||||
value: Props["value"]
|
||||
}
|
||||
| {
|
||||
onChange?: never
|
||||
value?: never
|
||||
}
|
||||
| {
|
||||
value: Props["value"]
|
||||
disabled: true
|
||||
onChange?: never
|
||||
}
|
||||
) &
|
||||
(
|
||||
| {
|
||||
label: Props["label"]
|
||||
"aria-label"?: never
|
||||
} // if label is passed, we forbid aria-label and vice versa
|
||||
| {
|
||||
label?: never
|
||||
"aria-label": Props["aria-label"]
|
||||
}
|
||||
)
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
(
|
||||
{ label, className, required, width, height, labelClassName, ...props },
|
||||
ref
|
||||
) => {
|
||||
const labelId = label?.replace(/\s/g, "-").toLowerCase()
|
||||
({ className, type, label, hideLabel, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
return (
|
||||
<div
|
||||
className={styles.wrapper}
|
||||
style={{
|
||||
width,
|
||||
height
|
||||
}}
|
||||
>
|
||||
{label && (
|
||||
<span className="flex w-full flex-row items-center">
|
||||
{label && !hideLabel ? (
|
||||
<label
|
||||
htmlFor={labelId}
|
||||
className={clsx(styles.label, labelClassName)}
|
||||
htmlFor={id}
|
||||
className={cn(
|
||||
"h-10 rounded-md border border-input bg-transparent px-3 py-2 text-sm font-medium text-muted-foreground",
|
||||
"rounded-br-none rounded-tr-none",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
) : null}
|
||||
{label && hideLabel ? (
|
||||
<label htmlFor={id} className="sr-only">
|
||||
{label}
|
||||
</label>
|
||||
) : null}
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
label && !hideLabel
|
||||
? "rounded-bl-none rounded-tl-none border-l-0"
|
||||
: "rounded-md",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
id={labelId}
|
||||
className={clsx(styles.input, label && styles.withLabel, className)}
|
||||
required={required}
|
||||
id={id}
|
||||
{...props}
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
...(props.style || {})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Input.displayName = "Input"
|
||||
|
||||
export default Input
|
||||
export { Input }
|
||||
|
@ -0,0 +1,128 @@
|
||||
import * as React from "react"
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||
import { cva } from "class-variance-authority"
|
||||
import { ChevronDown } from "react-feather"
|
||||
|
||||
import { cn } from "@lib/cn"
|
||||
|
||||
const NavigationMenu = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-10 flex flex-1 items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuPrimitive.Root>
|
||||
))
|
||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
||||
|
||||
const NavigationMenuList = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:bg-accent focus:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none bg-background hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent/50 data-[active]:bg-accent/50 h-10 py-2 px-4 group w-max"
|
||||
)
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
))
|
||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
||||
|
||||
const NavigationMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
||||
|
||||
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
||||
|
||||
const NavigationMenuViewport = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
))
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { cn } from "@lib/cn"
|
||||
import { PropsWithChildren } from "react"
|
||||
|
||||
export function PageTitle({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: PropsWithChildren<React.HTMLProps<HTMLHeadingElement>>) {
|
||||
return (
|
||||
<h1 className={cn("pb-2 pt-2 text-4xl font-bold", className)} {...props}>
|
||||
{children}
|
||||
</h1>
|
||||
)
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { cn } from "@lib/cn"
|
||||
import { PropsWithChildren } from "react"
|
||||
|
||||
export function PageWrapper({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: PropsWithChildren<React.HTMLProps<HTMLDivElement>>) {
|
||||
return (
|
||||
<div className={cn("mb-4 mt-4 flex flex-col gap-4", className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,35 +1,31 @@
|
||||
// largely from https://github.com/shadcn/taxonomy
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
import clsx from "clsx"
|
||||
import styles from "./popover.module.css"
|
||||
|
||||
type PopoverProps = PopoverPrimitive.PopoverProps
|
||||
import { cn } from "@lib/cn"
|
||||
|
||||
export function Popover({ ...props }: PopoverProps) {
|
||||
return <PopoverPrimitive.Root {...props} />
|
||||
}
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
Popover.Trigger = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
PopoverPrimitive.PopoverTriggerProps
|
||||
>(function PopoverTrigger({ ...props }, ref) {
|
||||
return <PopoverPrimitive.Trigger {...props} ref={ref} />
|
||||
})
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
Popover.Portal = PopoverPrimitive.Portal
|
||||
|
||||
Popover.Content = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
PopoverPrimitive.PopoverContentProps
|
||||
>(function PopoverContent({ className, ...props }, ref) {
|
||||
return (
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align="end"
|
||||
className={clsx(styles.root, className)}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent }
|
||||
|
@ -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 = () => (
|
||||
<li>
|
||||
<Card style={{ overflowY: "scroll" }}>
|
||||
{/* TODO: this is a bad way to do skeletons and is onlya ccurate on desktop */}
|
||||
<div style={{ display: "flex", gap: 16, marginBottom: 14 }}>
|
||||
<div className={styles.title}>
|
||||
{/* title */}
|
||||
<Skeleton width={80} height={32} />
|
||||
</div>
|
||||
{/* TODO: this is a bad way to do skeletons and is only accurate on desktop */}
|
||||
<CardHeader>
|
||||
<div style={{ display: "flex", gap: 16, marginBottom: 14 }}>
|
||||
<div className={styles.title}>
|
||||
{/* title */}
|
||||
<Skeleton width={80} height={32} />
|
||||
</div>
|
||||
|
||||
<div className={styles.badges}>
|
||||
<Skeleton width={60} height={32} />
|
||||
<Skeleton width={60} height={32} />
|
||||
<Skeleton width={60} height={32} />
|
||||
<div className={styles.badges}>
|
||||
<Skeleton width={60} height={32} />
|
||||
<Skeleton width={60} height={32} />
|
||||
<Skeleton width={60} height={32} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Skeleton width={100} height={32} />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Skeleton width={100} height={32} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</li>
|
||||
)
|
||||
|
@ -1,4 +0,0 @@
|
||||
.skeleton {
|
||||
background-color: var(--lighter-gray);
|
||||
border-radius: var(--radius);
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
import { cn } from "@lib/cn"
|
||||
import styles from "./spinner.module.css"
|
||||
|
||||
export const Spinner = () => <div className={styles.spinner} />
|
||||
export const Spinner = ({ className }: { className?: string }) => (
|
||||
<div className={cn(styles.spinner, className)} />
|
||||
)
|
||||
|
@ -0,0 +1,55 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@lib/cn"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-transparent p-1 text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:border-b-2 data-[state=active]:border-foreground data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue