├── components ├── layout │ ├── footer.tsx │ ├── paragraph.tsx │ ├── sticky-footer.tsx │ ├── sticky-header.tsx │ ├── sticky-sidebar.tsx │ └── responsive-sidebar-button.tsx ├── ui │ ├── aspect-ratio.tsx │ ├── skeleton.tsx │ ├── collapsible.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── input.tsx │ ├── separator.tsx │ ├── progress.tsx │ ├── toaster.tsx │ ├── slider.tsx │ ├── checkbox.tsx │ ├── tooltip.tsx │ ├── switch.tsx │ ├── badge.tsx │ ├── hover-card.tsx │ ├── popover.tsx │ ├── toggle.tsx │ ├── avatar.tsx │ ├── radio-group.tsx │ ├── alert.tsx │ ├── scroll-area.tsx │ ├── button.tsx │ ├── tabs.tsx │ ├── card.tsx │ ├── accordion.tsx │ ├── calendar.tsx │ ├── table.tsx │ ├── dialog.tsx │ ├── use-toast.ts │ ├── select.tsx │ ├── form.tsx │ ├── sheet.tsx │ ├── alert-dialog.tsx │ ├── toast.tsx │ ├── command.tsx │ ├── navigation-menu.tsx │ ├── context-menu.tsx │ ├── dropdown-menu.tsx │ └── menubar.tsx ├── typography │ ├── code.tsx │ └── link.tsx └── helpers │ ├── AfterSSR.tsx │ ├── FakeParagraphs.tsx │ └── FakeWordList.tsx ├── next.config.js ├── postcss.config.js ├── convex ├── auth.config.ts ├── _generated │ ├── api.js │ ├── api.d.ts │ ├── dataModel.d.ts │ ├── server.js │ └── server.d.ts ├── tsconfig.json ├── schema.ts ├── myFunctions.ts └── README.md ├── .env.local.example ├── app ├── layouts │ ├── layout.tsx │ ├── sticky-header-footer-below-fold │ │ └── page.tsx │ ├── sticky-header-sticky-footer │ │ └── page.tsx │ ├── sticky-header-sidebar-footer-below-fold │ │ └── page.tsx │ ├── sticky-sides-flex-content-simple │ │ └── page.tsx │ ├── sticky-header-sidebar-footer-inside │ │ └── page.tsx │ ├── sticky-header-sidebar-sticky-footer │ │ └── page.tsx │ ├── sticky-sides-flex-content │ │ └── page.tsx │ ├── sticky-header-sidebar-footer-below-fold-responsive │ │ └── page.tsx │ └── page.tsx ├── layout.tsx ├── ConvexProviderWithDescope.ts ├── ConvexClientProvider.tsx ├── sign-in │ └── page.tsx ├── globals.css └── page.tsx ├── components.json ├── .gitignore ├── tsconfig.json ├── lib └── utils.tsx ├── .eslintrc.cjs ├── tailwind.config.js ├── package.json └── README.md /components/layout/footer.tsx: -------------------------------------------------------------------------------- 1 | import { se } from "@/lib/utils"; 2 | 3 | export const Footer = se("footer", "border-t"); 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /components/layout/paragraph.tsx: -------------------------------------------------------------------------------- 1 | import { se } from "@/lib/utils"; 2 | 3 | export const Paragraph = se("p", "leading-7 [&:not(:first-child)]:mt-6"); 4 | -------------------------------------------------------------------------------- /components/layout/sticky-footer.tsx: -------------------------------------------------------------------------------- 1 | import { se } from "@/lib/utils"; 2 | 3 | export const StickyFooter = se( 4 | "footer", 5 | "sticky bottom-0 z-50 w-full border-t bg-background/80 backdrop-blur" 6 | ); 7 | -------------------------------------------------------------------------------- /components/layout/sticky-header.tsx: -------------------------------------------------------------------------------- 1 | import { se } from "@/lib/utils"; 2 | 3 | export const StickyHeader = se( 4 | "header", 5 | "sticky top-0 z-50 w-full border-b bg-background/80 backdrop-blur" 6 | ); 7 | -------------------------------------------------------------------------------- /components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /convex/auth.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | providers: [ 3 | { 4 | domain: process.env.NEXT_PUBLIC_ISSUER_URL, 5 | applicationID: process.env.NEXT_PUBLIC_DESCOPE_PROJECT_ID, 6 | }, 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /components/typography/code.tsx: -------------------------------------------------------------------------------- 1 | import { se } from "@/lib/utils"; 2 | 3 | export const Code = se( 4 | "code", 5 | "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold" 6 | ); 7 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | CONVEX_DEPLOYMENT=dev:dynamic-example 2 | 3 | NEXT_PUBLIC_CONVEX_URL=https://dynamic-example.convex.cloud 4 | 5 | NEXT_PUBLIC_DESCOPE_PROJECT_ID="" 6 | NEXT_PUBLIC_ISSUER_URL="" 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /components/helpers/AfterSSR.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, ReactNode } from "react"; 2 | 3 | export function AfterSSR({ children }: { children: ReactNode }) { 4 | const [show, setShow] = useState(false); 5 | useEffect(() => { 6 | setShow(true); 7 | }, []); 8 | if (!show) { 9 | return null; 10 | } 11 | return children; 12 | } 13 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /app/layouts/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { ReactNode } from "react"; 3 | 4 | export const metadata: Metadata = { 5 | title: "Layout showcase - you can delete me", 6 | description: 'You can delete the whole "layouts" directory', 7 | }; 8 | 9 | export default function LayoutsLayout({ children }: { children: ReactNode }) { 10 | return children; 11 | } 12 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /components/layout/sticky-sidebar.tsx: -------------------------------------------------------------------------------- 1 | import { cn, fr } from "@/lib/utils"; 2 | import { ScrollArea } from "@/components/ui/scroll-area"; 3 | 4 | export const StickySidebar = fr(function StickySidebar( 5 | { className, children, ...props }, 6 | ref 7 | ) { 8 | return ( 9 | 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /components/helpers/FakeParagraphs.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from "react"; 2 | import { Paragraph } from "../layout/paragraph"; 3 | import Jabber from "jabber"; 4 | 5 | const jabber = new Jabber(); 6 | 7 | export const FakeParagraphs = memo(function FakeParagraphs({ 8 | count, 9 | words, 10 | }: { 11 | count: number; 12 | words: number; 13 | }) { 14 | return ( 15 | <> 16 | {[...Array(count)].map((_, i) => ( 17 | {jabber.createParagraph(words)} 18 | ))} 19 | 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /convex/_generated/api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.10.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { anyApi } from "convex/server"; 13 | 14 | /** 15 | * A utility for referencing Convex functions in your app's API. 16 | * 17 | * Usage: 18 | * ```js 19 | * const myFunctionReference = api.myModule.myFunction; 20 | * ``` 21 | */ 22 | export const api = anyApi; 23 | export const internal = anyApi; 24 | -------------------------------------------------------------------------------- /components/helpers/FakeWordList.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from "react"; 2 | import Jabber from "jabber"; 3 | 4 | const jabber = new Jabber(); 5 | 6 | export const FakeWordList = memo(function FakeWordList({ 7 | count, 8 | length, 9 | capitalize, 10 | }: { 11 | count: number; 12 | length: [number, number]; 13 | capitalize?: boolean; 14 | }) { 15 | return ( 16 | <> 17 | {[...Array(count)].map((_, i) => ( 18 |
19 | {jabber.createWord( 20 | length[0] + Math.floor(Math.random() * (length[1] - length[0])), 21 | capitalize 22 | )} 23 |
24 | ))} 25 | 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { Metadata } from "next"; 3 | import { Inter } from "next/font/google"; 4 | import ConvexClientProvider from "./ConvexClientProvider"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export const metadata: Metadata = { 9 | title: "My App Title", 10 | description: "My app description", 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: { 16 | children: React.ReactNode; 17 | }) { 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /components/typography/link.tsx: -------------------------------------------------------------------------------- 1 | import { cn, fr } from "@/lib/utils"; 2 | import NextLink, { LinkProps } from "next/link"; 3 | 4 | export const Link = fr< 5 | HTMLAnchorElement, 6 | Omit, keyof LinkProps> & 7 | LinkProps & { 8 | children?: React.ReactNode; 9 | } & React.RefAttributes 10 | >(function Link({ className, children, ...props }, ref) { 11 | return ( 12 | 20 | {children} 21 | 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /app/ConvexProviderWithDescope.ts: -------------------------------------------------------------------------------- 1 | import { useSession, getSessionToken } from "@descope/nextjs-sdk/client"; 2 | import { useCallback, useMemo } from "react"; 3 | 4 | export default function useAuthFromDescope() { 5 | const { isSessionLoading, isAuthenticated } = useSession(); 6 | 7 | // SDK should automatically refresh the token when using getSessionToken() if refresh token is still active. 8 | const fetchAccessToken = useCallback(async () => { 9 | return getSessionToken(); 10 | }, []); 11 | return useMemo( 12 | () => ({ 13 | isLoading: isSessionLoading, 14 | isAuthenticated: isAuthenticated ?? false, 15 | fetchAccessToken, 16 | }), 17 | [isSessionLoading, isAuthenticated, fetchAccessToken] 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /app/ConvexClientProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { ReactNode } from "react"; 3 | import { ConvexProviderWithAuth, ConvexReactClient } from "convex/react"; 4 | import useAuthFromDescope from "./ConvexProviderWithDescope"; 5 | import { AuthProvider } from "@descope/nextjs-sdk"; 6 | 7 | const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); 8 | 9 | export default function ConvexClientProvider({ 10 | children, 11 | }: { 12 | children: ReactNode; 13 | }) { 14 | return ( 15 | 16 | 17 | {children} 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Descope } from "@descope/nextjs-sdk"; 4 | 5 | export default function SignInPage() { 6 | return ( 7 |
8 |
13 | 14 |
15 |
16 | 21 |
22 |
23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /app/layouts/sticky-header-footer-below-fold/page.tsx: -------------------------------------------------------------------------------- 1 | import { FakeParagraphs } from "@/components/helpers/FakeParagraphs"; 2 | import { Footer } from "@/components/layout/footer"; 3 | import { Paragraph } from "@/components/layout/paragraph"; 4 | import { StickyHeader } from "@/components/layout/sticky-header"; 5 | 6 | export default function Layout() { 7 | return ( 8 | <> 9 | Sticky header 10 | {/* For Footer to appear below the fold, the subtrahend 11 | inside calc() must be the same height as the header */} 12 |
13 | Main content 14 | 15 |
16 |
Footer below fold
17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |