diff --git a/.nvmrc b/.nvmrc
index 7950a445..3f430af8 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v18.17.0
+v18
diff --git a/package.json b/package.json
index d0cbaf88..aa3c244d 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,10 @@
{
- "name": "@luxdefi/ui",
+ "name": "@luxdefi/ui/cli",
"version": "0.0.1",
- "private": true,
"license": "MIT",
"type": "module",
"author": {
- "name": "luxdefi",
+ "name": "@lx/ui",
"url": "https://twitter.com/luxdefi"
},
"workspaces": [
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 44baf32f..67d04940 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,4 +1,5 @@
- "name": "luxdefi-ui",
+{
+ "name": "@luxdefi/ui/cli",
"version": "0.6.0",
"description": "Add components to your apps.",
"publishConfig": {
diff --git a/packages/ui/blocks/components/accordian-block.tsx b/packages/ui/blocks/components/accordian-block.tsx
new file mode 100644
index 00000000..6597c248
--- /dev/null
+++ b/packages/ui/blocks/components/accordian-block.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+ ApplyTypography
+} from '../../primitives'
+
+import { cn } from '../../util'
+
+import type { Block, AccordianBlock } from '../def'
+
+const AccordianBlockComponent: React.FC<{
+ block: Block
+ className?: string
+}> = ({
+ block,
+ className=''
+}) => {
+
+ if (block.blockType !== 'accordian') {
+ return <>accordian block required>
+ }
+
+ const accordian = block as AccordianBlock
+
+ return (
+
+ {accordian.items.map((item, index) => (
+
+
+
+ {/* styles specific to accordion. From old site */}
+ {item.trigger}
+
+
+
+
+ {(typeof item.content === 'string') ? ({item.content}
) : item.content}
+
+
+
+ ))}
+
+ )
+}
+
+export default AccordianBlockComponent
diff --git a/packages/ui/blocks/components/banner-block.tsx b/packages/ui/blocks/components/banner-block.tsx
new file mode 100644
index 00000000..e550fc24
--- /dev/null
+++ b/packages/ui/blocks/components/banner-block.tsx
@@ -0,0 +1,103 @@
+
+import React from 'react'
+
+import { Dimensions, type TShirtSize } from '../../types'
+import type { Block, BannerBlock } from '../def'
+
+import VideoBlockComponent from './video-block'
+import CTABlockComponent from './cta-block'
+
+type BannerGrouping = 'all-separate' | 'title-media-cta' | 'titleAndMedia-cta'
+
+const BannerBlockComponent: React.FC<{
+ block: Block,
+ videoSize?: TShirtSize
+ videoConstraint?: Dimensions
+ grouping?: BannerGrouping
+ groupingClasses?: string[] // count should match number of siblings in the chosen grouping
+ ctaItemClassName?: string
+}> = ({
+ block,
+ grouping = 'titleAndMedia-cta',
+ groupingClasses=[],
+ ctaItemClassName='',
+ videoSize='md',
+ videoConstraint
+}) => {
+
+ if (block.blockType !== 'banner') {
+ return <>banner block required>
+ }
+ const banner = block as BannerBlock
+
+ if (grouping === 'title-media-cta') {
+ const titleClasses = (groupingClasses && groupingClasses[0]) ? groupingClasses[0] : ''
+ const mediaClasses = (groupingClasses && groupingClasses[1]) ? groupingClasses[1] : ''
+ const ctaClasses = (groupingClasses && groupingClasses[2]) ? groupingClasses[2] : ''
+ return (<>
+
+
{banner.title}
+ {banner.byline && ({banner.byline} )}
+
+
+ {banner.contentBefore && banner.contentBefore}
+ {banner.video && (
+
+ )}
+ {banner.contentAfter && banner.contentAfter }
+
+ {banner.cta && (
+
+
+
+ )}
+ >)
+ }
+ else if (grouping === 'titleAndMedia-cta') {
+ const titleAndMediaClasses = (groupingClasses && groupingClasses[0]) ? groupingClasses[0] : ''
+ const ctaClasses = (groupingClasses && groupingClasses[1]) ? groupingClasses[1] : ''
+ return (<>
+
+
{banner.title}
+ {banner.byline && ({banner.byline} )}
+ {banner.contentBefore && banner.contentBefore}
+ {banner.video && (
+
+ )}
+ {banner.contentAfter && banner.contentAfter }
+
+ {banner.cta && (
+
+
+
+ )}
+ >)
+ }
+
+ const titleClasses = (groupingClasses && groupingClasses[0]) ? groupingClasses[0] : ''
+ const bylineClasses = (groupingClasses && groupingClasses[1]) ? groupingClasses[1] : ''
+ const contentBeforeClasses = (groupingClasses && groupingClasses[2]) ? groupingClasses[2] : ''
+ const mediaClasses = (groupingClasses && groupingClasses[3]) ? groupingClasses[3] : ''
+ const contentAfterClasses = (groupingClasses && groupingClasses[4]) ? groupingClasses[4] : ''
+ const ctaClasses = (groupingClasses && groupingClasses[5]) ? groupingClasses[5] : ''
+
+ return (<>
+ {banner.title}
+ {banner.byline && ({banner.byline} )}
+ {banner.contentBefore && (banner.contentBefore
)}
+ {banner.video && (
+
+ )}
+ {banner.contentAfter && (banner.contentAfter
)}
+ {banner.cta && (
+
+
+
+ )}
+ >)
+}
+
+export {
+ BannerBlockComponent as default,
+ type BannerGrouping as AssetBannderGroupingType
+}
diff --git a/packages/ui/blocks/components/block-factory.tsx b/packages/ui/blocks/components/block-factory.tsx
new file mode 100644
index 00000000..676b05df
--- /dev/null
+++ b/packages/ui/blocks/components/block-factory.tsx
@@ -0,0 +1,52 @@
+import React from 'react'
+
+import type * as B from '../def'
+
+import CardBlockComponent from './card-block'
+import HeadingBlockComponent from './heading-block'
+import CTABlockComponent from './cta-block'
+import SpaceBlockComponent from './space-block'
+import ImageBlockComponent from './image-block'
+import VideoBlockComponent from './video-block'
+import AccordianBlockComponent from './accordian-block'
+import GroupBlockComponent from './group-block'
+
+const BlockFactory: React.FC<{
+ block: B.Block
+ className?: string
+}> = ({
+ block,
+ className=''
+}) => {
+
+ switch (block.blockType) {
+ case 'group': {
+ return
+ }
+ case 'card': {
+ return
+ }
+ case 'cta': {
+ return
+ }
+ case 'heading': {
+ return
+ }
+ case 'space': {
+ return
+ }
+ case 'video': {
+ return
+ }
+ case 'image': {
+ return
+ }
+ case 'accordian': {
+ return
+ }
+ }
+
+ return <>unknown block type>
+}
+
+export default BlockFactory
diff --git a/packages/ui/blocks/components/card-block.tsx b/packages/ui/blocks/components/card-block.tsx
new file mode 100644
index 00000000..595b7261
--- /dev/null
+++ b/packages/ui/blocks/components/card-block.tsx
@@ -0,0 +1,214 @@
+import React from 'react'
+
+import type { Dimensions, LinkDef, TShirtDimensions } from '../../types'
+
+import {
+ ApplyTypography,
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+ type TypographySize
+} from '../../primitives'
+
+import {
+ Icons,
+ LinkElement
+} from '../../common'
+
+import {
+ getSpecifierData,
+ getPrimaryStartingWith,
+ getDim,
+} from '../../util/specifier'
+
+import type { Block, CardBlock } from '../def'
+
+
+import ImageBlockComponent from './image-block'
+import VideoBlockComponent from './video-block'
+import CTABlockComponent from './cta-block'
+
+const ArrowLinkElement: React.FC<{
+ def: LinkDef,
+}> = ({
+ def
+}) => (
+ }
+ iconAfter
+ />
+)
+
+const getTypographySize = (s: string): TypographySize => (
+ getSpecifierData(
+ s,
+ (s: string) => (getPrimaryStartingWith(s, 'typography')),
+ (s: string): TypographySize | undefined => {
+ const subTokenArray = s.split('-')
+ return subTokenArray[subTokenArray.length - 1] as TypographySize
+ },
+ 'responsive'
+ ) as TypographySize
+)
+
+const getSmallIconDim = (s: string): Dimensions | undefined => (
+ getSpecifierData(
+ s,
+ (s: string) => (getPrimaryStartingWith(s, 'small-icon')),
+ getDim,
+ )
+)
+
+const CardBlockComponent: React.FC<{
+ block: Block
+ className?: string
+ contentClassName?: string
+}> = ({
+ block,
+ className='',
+ contentClassName=''
+}) => {
+
+ if (block.blockType !== 'card') {
+ return <>card block required>
+ }
+
+ const card = block as CardBlock
+ const has = (s: string) => (card.specifiers?.includes(s))
+
+ const ghost = has('ghost') // no outer padding, no borders, larger title, all left-aligned bg is same (default)
+
+ const contentclx = (has('content-left') ? 'items-start ' : 'items-center ') + contentClassName
+ const disabledBorder = (has('appear-disabled' ) ? ' border-muted-4' : ' border-muted-3')
+ const outerBorder = ((has('no-outer-border') || ghost) ? ' border-0' : '')
+ const innerBorder = (ghost ? ' border-0' : '')
+ const paddingclx = ghost ? ' px-0 py-0' : ' px-6 py-3'
+ const mainGap = ghost ? ' gap-2' : ''
+ const disabledText = (has('appear-disabled') ? ' text-muted-2' : '')
+ const disabledTypoText = (has('appear-disabled') ? ' typography-p:text-muted-2' : '')
+ const bgclx = (has('bg-card') ? ' bg-level-1' : '')
+ const titleclx = (has('heading-style-title') ? ' font-heading text-base leading-tight' : '') +
+ (ghost ? ' text-left md:text-xl' : '')
+
+ const typoSize: TypographySize = (card.specifiers) ? getTypographySize(card.specifiers) : 'responsive'
+ const typoclx = (typoSize === 'sm') ? 'typography-sm typography-p:text-sm ' : (typoSize === 'lg') ? 'typography-lg ' : ''
+
+ const contentBefore = has('content-before')
+ const iconInline = has('icon-inline')
+ const contentOnHover = has('reveal-content-on-hover')
+ //const smallIconDim = (contentOnHover && card.specifiers) ? getSmallIconDim(card.specifiers) : undefined
+
+ const Header: React.FC<{
+ inContent?: boolean
+ className?: string
+ }> = ({
+ inContent=false,
+ className=''
+ }) => (
+ (card.title || card.byline || card.icon) ? (
+
+
+ {(card.icon && !card.iconAfter ) && (
{card.icon}
)}
+ {card.title && (
+
+ {card.title}
+
+ )}
+ {(card.icon && card.iconAfter) && (
{card.icon}
)}
+
+ {card.byline && ({card.byline} )}
+
+ ) : null
+ )
+
+ const MediaAndContent: React.FC<{
+ className?: string
+ }> = ({
+ className=''
+ }) => (has('media-left') ? (
+ // media left layout
+
+ {card.media && (
+
+
+
+ )}
+ {card.content && (
+
+ {(typeof card.content === 'string') ? ({card.content}
) : card.content}
+
+ )}
+
+ ) : ( // default layout
+
+ {contentOnHover && ()}
+ {card.content && contentBefore && (
+ (typeof card.content === 'string') ? ({card.content}
) : card.content
+ )}
+ {card.media && (card.media.blockType === 'image' ? (
+
+ ) : (
+
+ ))}
+ {card.content && !contentBefore && (
+ (typeof card.content === 'string') ? ({card.content}
) : card.content
+ )}
+
+ ))
+
+ const Footer: React.FC = () => ( !card.cta ? null : (has('links-w-arrow') ? (
+ // links w arrow
+
+ ( )}
+ />
+
+ ) : ( // default
+
+
+
+ )))
+
+ return (
+
+
+
+
+
+ )
+}
+
+export default CardBlockComponent
diff --git a/packages/ui/blocks/components/cta-block.tsx b/packages/ui/blocks/components/cta-block.tsx
new file mode 100644
index 00000000..37da8004
--- /dev/null
+++ b/packages/ui/blocks/components/cta-block.tsx
@@ -0,0 +1,58 @@
+import React from 'react'
+
+import type { LinkDef, ButtonDef} from '../../types'
+import type { ButtonSizes } from '../../primitives'
+import { ActionButton, LinkElement } from '../../common'
+import type { Block, CTABlock } from '../def'
+
+const CtaBlockComponent: React.FC<{
+ block: Block,
+ itemClassName?: string,
+ itemSize?: ButtonSizes
+ renderLink?: (def: LinkDef, key: any) => JSX.Element
+ renderButton?: (def: ButtonDef, key: any) => JSX.Element
+}> = ({
+ block,
+ itemClassName='',
+ itemSize, // do not provide default. this is an override to the def
+ renderLink,
+ renderButton
+}) => {
+
+ if (block.blockType !== 'cta') {
+ return <>cta block required>
+ }
+
+ const { elements } = block as CTABlock
+
+ return (
+ <>
+ {elements.map((element, index) => {
+ if ((element as any).title) {
+ const def = element as LinkDef
+ return renderLink ? renderLink(def, index) : (
+
+ )
+ }
+ else {
+ const def = element as ButtonDef
+ return renderButton ? renderButton(def, index) : (
+
+ )
+ }
+ })}
+ >
+ )
+}
+
+export default CtaBlockComponent
diff --git a/packages/ui/blocks/components/group-block.tsx b/packages/ui/blocks/components/group-block.tsx
new file mode 100644
index 00000000..9e27455e
--- /dev/null
+++ b/packages/ui/blocks/components/group-block.tsx
@@ -0,0 +1,85 @@
+import React from 'react'
+
+import { type Breakpoint, Breakpoints} from '../../types'
+import { cn } from '../../util'
+
+import type { Block, GroupBlock } from '../def'
+import BlockFactory from './block-factory'
+
+// eg: 'layout-grid-2-starting-md'
+// see comments below regarding dynamic classes and the safelist
+const getLayoutInfo = (s: string): {
+ layout: string
+ spec: any
+} | undefined => {
+ const tokenArray = s.split(' ')
+ const layoutToken = tokenArray.find((tok) => (tok.startsWith('layout-')))
+ if (layoutToken) {
+ const subtokens = layoutToken.split('-')
+ const layout = subtokens[1]
+ let spec: any = {}
+ switch (layout) {
+ case 'grid': {
+ const columns = parseInt(subtokens[2], 10)
+ const starting = subtokens[4] as Breakpoint
+ if (Number.isNaN(columns) || columns < 2 || columns > 6 || !Breakpoints.includes(starting)) {
+ return undefined
+ }
+ spec = {
+ columns,
+ starting
+ }
+ } break
+ // no other types supported yet
+ }
+ return {
+ layout,
+ spec
+ }
+ }
+ return undefined
+}
+
+const GroupBlockComponent: React.FC<{
+ block: Block
+ className?: string,
+}> = ({
+ block,
+ className=''
+}) => {
+
+ if (block.blockType !== 'group') {
+ return <>group block required>
+ }
+
+ const group = block as GroupBlock
+
+ // only one supported so fat
+ if (group.specifiers?.includes('layout')) {
+ const layoutSpec = getLayoutInfo(group.specifiers)
+ if (!layoutSpec) {
+ return <>invalid or missing layout specifier in group block!>
+ }
+
+ if (layoutSpec.layout === 'grid') {
+ const { elements } = group
+ const { spec: {starting, columns} } = layoutSpec
+
+ // https://tailwindcss.com/docs/content-configuration#dynamic-class-names
+ // All variants in use MUST be in style/safelist.tailwind.js
+ const clazzName = cn('grid xs:grid-cols-1 gap-2 sm:gap-3',
+ `${starting}:grid-cols-${columns} `,
+ className)
+ return (
+
+ {elements.map((block, index) => (
+
+ ))}
+
+ )
+ }
+ }
+ return <>>
+}
+
+export default GroupBlockComponent
diff --git a/packages/ui/blocks/components/heading-block.tsx b/packages/ui/blocks/components/heading-block.tsx
new file mode 100644
index 00000000..0398ee2c
--- /dev/null
+++ b/packages/ui/blocks/components/heading-block.tsx
@@ -0,0 +1,45 @@
+import React from 'react'
+
+import type { Block, HeadingBlock } from '../def'
+
+const HeadingBlockComponent: React.FC<{
+ block: Block,
+ className?: string
+}> = ({
+ block,
+ className=''
+}) => {
+
+ if (block.blockType !== 'heading') {
+ return <>heading block required>
+ }
+ const heading = block as HeadingBlock
+
+ let Tag: React.ElementType = 'h3' // default
+
+ switch (heading.level) {
+ case 0: {
+ Tag = 'p'
+ } break
+ case 1: {
+ Tag = 'h1'
+ } break
+ case 2: {
+ Tag = 'h2'
+ } break
+
+ case 4: {
+ Tag = 'h4'
+ } break
+ case 5: {
+ Tag = 'h5'
+ } break
+ case 6: {
+ Tag = 'h6'
+ } break
+ }
+
+ return {heading.heading}
+}
+
+export default HeadingBlockComponent
diff --git a/packages/ui/blocks/components/image-block.tsx b/packages/ui/blocks/components/image-block.tsx
new file mode 100644
index 00000000..cf3a99f3
--- /dev/null
+++ b/packages/ui/blocks/components/image-block.tsx
@@ -0,0 +1,35 @@
+import React from 'react'
+
+import Image from 'next/image'
+
+import { constrain } from '../../util'
+import type { Dimensions } from '../../types'
+
+import type { Block, ImageBlock } from '../def'
+
+const ImageBlockComponent: React.FC<{
+ block: Block
+ className?: string
+ constraint?: Dimensions
+}> = ({
+ block,
+ className='',
+ constraint
+}) => {
+
+ if (block.blockType !== 'image') {
+ return <>iamge block required>
+ }
+
+ const b = block as ImageBlock
+
+ const dim = b.dim as Dimensions
+ const conDim = (constraint ? constrain(dim, constraint) : dim)
+
+ return (
+
+ )
+}
+
+export default ImageBlockComponent
+
diff --git a/packages/ui/blocks/components/index.ts b/packages/ui/blocks/components/index.ts
new file mode 100644
index 00000000..06f1ac5a
--- /dev/null
+++ b/packages/ui/blocks/components/index.ts
@@ -0,0 +1,23 @@
+import AccordianBlock from './accordian-block'
+import BannerBlock from './banner-block'
+import BlockFactory from './block-factory'
+import CardBlock from './card-block'
+import CTABlock from './cta-block'
+import GroupBlock from './group-block'
+import HeadingBlock from './heading-block'
+import ImageBlock from './image-block'
+import VideoBlock from './video-block'
+import SpaceBlock from './space-block'
+
+export {
+ AccordianBlock as AccordianBlockComponent,
+ BannerBlock as BannerBlockComponent,
+ BlockFactory,
+ CardBlock as CardBlockComponent,
+ CTABlock as CTABlockComponent,
+ GroupBlock as GroupBlockComponent,
+ HeadingBlock as HeadingBlockComponent,
+ ImageBlock as ImageBlockComponent,
+ VideoBlock as VideoBlockComponent,
+ SpaceBlock as SpaceBlockComponent,
+}
\ No newline at end of file
diff --git a/packages/ui/blocks/components/space-block.tsx b/packages/ui/blocks/components/space-block.tsx
new file mode 100644
index 00000000..9ef1a8a5
--- /dev/null
+++ b/packages/ui/blocks/components/space-block.tsx
@@ -0,0 +1,47 @@
+import React from 'react'
+
+import type { Block, SpaceBlock } from '../def'
+
+const SpaceBlockComponent: React.FC<{
+ block?: Block
+ className?: string
+}> = ({
+ block,
+ className=''
+}) => {
+
+ if (block && block.blockType !== 'space') {
+ return <>space block required>
+ }
+
+ const size = (block) ? ((block as SpaceBlock).level ?? 0) : 0
+
+ let Tag: React.ElementType = 'div' // corresponds to 0
+
+ switch(size) {
+ case 1:
+ Tag = 'h1'
+ break
+ case 2:
+ Tag = 'h2'
+ break
+ case 3:
+ Tag = 'h3'
+ break
+ case 4:
+ Tag = 'h4'
+ break
+ case 5:
+ Tag = 'h5'
+ break
+ case 6:
+ Tag = 'h6'
+ break
+ }
+
+ return (
+
+ )
+}
+
+export default SpaceBlockComponent
diff --git a/packages/ui/blocks/components/video-block.tsx b/packages/ui/blocks/components/video-block.tsx
new file mode 100644
index 00000000..d87cca6b
--- /dev/null
+++ b/packages/ui/blocks/components/video-block.tsx
@@ -0,0 +1,101 @@
+'use client'
+import React, { useEffect, useLayoutEffect, useState } from 'react'
+
+import Image from 'next/image'
+
+import type { Dimensions, TShirtSize, TShirtDimensions } from '../../types'
+import { constrain, asNum } from '../../util'
+import type { Block, VideoBlock } from '../def'
+import { VideoPlayer } from '../../primitives'
+
+const VideoBlockComponent: React.FC<{
+ block: Block
+ className?: string
+ usePoster?: boolean
+ size?: TShirtSize
+ constraint?: Dimensions
+}> = ({
+ block,
+ className='',
+ usePoster=false,
+ size='md',
+ constraint
+}) => {
+
+ const [_dim, setDim] = useState(undefined)
+
+ const onResize = () => {
+ const height = window.innerHeight
+ const width = window.innerWidth
+
+ setDim({
+ w: width,
+ h: height
+ })
+ }
+
+ useEffect(() => {
+ if (window) {
+ window.addEventListener('resize', onResize)
+ return () => window.removeEventListener('resize', onResize)
+ }
+ }, [])
+
+ useLayoutEffect(() => {
+ onResize()
+ }, [])
+
+
+ if (block.blockType !== 'video') {
+ return <>video block required>
+ }
+
+ const b = block as VideoBlock
+ if (b.sizing?.vh) {
+ const H = `${b.sizing.vh}vh`
+ const ar = asNum(b.dim.md.w) / asNum(b.dim.md.h)
+ // serverside, generate the css for the correctly sized poster image
+ if (!_dim) {
+ return
+ }
+ else {
+ const height = ((b.sizing.vh / 100) * asNum(_dim.h))
+ const dim = {
+ h: height,
+ w: height * ar
+ }
+ return ((
+
+ ))
+ }
+ }
+
+ const videoDims = b.dim as TShirtDimensions
+ const dim = ((size && size in videoDims) ? videoDims[size] : videoDims.md) as Dimensions
+ const conDim = (constraint ? constrain(dim, constraint) : dim)
+ return usePoster ? (
+
+ ) : (
+
+ )
+}
+
+export default VideoBlockComponent
diff --git a/packages/ui/blocks/def/accordian-block.ts b/packages/ui/blocks/def/accordian-block.ts
new file mode 100644
index 00000000..d8a97192
--- /dev/null
+++ b/packages/ui/blocks/def/accordian-block.ts
@@ -0,0 +1,14 @@
+import React from 'react'
+import type Block from './block'
+
+interface AccordianBlock extends Block {
+ blockType: 'accordian'
+ items: {
+ trigger: string
+ content: React.ReactNode
+ }[]
+}
+
+export {
+ type AccordianBlock as default
+}
diff --git a/packages/ui/blocks/def/banner-block.ts b/packages/ui/blocks/def/banner-block.ts
new file mode 100644
index 00000000..cf5632ee
--- /dev/null
+++ b/packages/ui/blocks/def/banner-block.ts
@@ -0,0 +1,18 @@
+import React from 'react'
+import type Block from './block'
+import type VideoBlock from './video-block'
+import type CTABlock from './cta-block'
+
+interface BannerBlock extends Block {
+ blockType: 'banner'
+ title: string
+ byline?: string
+ contentBefore?: React.ReactNode
+ video?: VideoBlock
+ contentAfter?: React.ReactNode
+ cta?: CTABlock
+}
+
+export {
+ type BannerBlock as default
+}
diff --git a/packages/ui/blocks/def/block.ts b/packages/ui/blocks/def/block.ts
new file mode 100644
index 00000000..370b0b07
--- /dev/null
+++ b/packages/ui/blocks/def/block.ts
@@ -0,0 +1,7 @@
+interface Block {
+ blockType: string
+}
+
+export {
+ type Block as default
+}
\ No newline at end of file
diff --git a/packages/ui/blocks/def/card-block.ts b/packages/ui/blocks/def/card-block.ts
new file mode 100644
index 00000000..77606be3
--- /dev/null
+++ b/packages/ui/blocks/def/card-block.ts
@@ -0,0 +1,24 @@
+import React from 'react'
+
+import { Icon } from '../../types'
+
+import type CTABlock from './cta-block'
+import type Block from './block'
+import type ImageBlock from './image-block'
+import VideoBlock from './video-block'
+
+interface CardBlock extends Block {
+ blockType: 'card'
+ specifiers?: string // 'media-left' or 'appear-disabled' or 'no-borders', etc... can be combined
+ title?: string
+ byline?: string
+ icon?: Icon // for title area
+ iconAfter?: boolean
+ media?: ImageBlock | VideoBlock
+ content?: React.ReactNode
+ cta?: CTABlock
+}
+
+export {
+ type CardBlock as default
+}
diff --git a/packages/ui/blocks/def/cta-block.ts b/packages/ui/blocks/def/cta-block.ts
new file mode 100644
index 00000000..e5d2969c
--- /dev/null
+++ b/packages/ui/blocks/def/cta-block.ts
@@ -0,0 +1,12 @@
+import type { LinkDef, ButtonDef } from '../../types'
+import type Block from './block'
+
+interface CTABlock extends Block {
+ blockType: 'cta'
+ specifiers?: string // eg, 'nav' (hint to renderer)
+ elements: (LinkDef | ButtonDef)[]
+}
+
+export {
+ type CTABlock as default
+}
diff --git a/packages/ui/blocks/def/element-block.ts b/packages/ui/blocks/def/element-block.ts
new file mode 100644
index 00000000..e4c9fca1
--- /dev/null
+++ b/packages/ui/blocks/def/element-block.ts
@@ -0,0 +1,11 @@
+import React from 'react'
+import type Block from './block'
+
+interface ElementBlock extends Block {
+ blockType: 'element'
+ element: React.ReactNode | JSX.Element
+}
+
+export {
+ type ElementBlock as default
+}
\ No newline at end of file
diff --git a/packages/ui/blocks/def/group-block.ts b/packages/ui/blocks/def/group-block.ts
new file mode 100644
index 00000000..583a9620
--- /dev/null
+++ b/packages/ui/blocks/def/group-block.ts
@@ -0,0 +1,11 @@
+import type Block from './block'
+
+interface GroupBlock extends Block {
+ blockType: 'group'
+ specifiers?: string // grid-2 slider disabled or whatever
+ elements: Block[]
+}
+
+export {
+ type GroupBlock as default
+}
diff --git a/packages/ui/blocks/def/heading-block.ts b/packages/ui/blocks/def/heading-block.ts
new file mode 100644
index 00000000..1355a796
--- /dev/null
+++ b/packages/ui/blocks/def/heading-block.ts
@@ -0,0 +1,12 @@
+import React from 'react'
+import type Block from './block'
+
+interface HeadingBlock extends Block {
+ blockType: 'heading'
+ heading: string
+ level?: number
+}
+
+export {
+ type HeadingBlock as default
+}
\ No newline at end of file
diff --git a/packages/ui/blocks/def/image-block.ts b/packages/ui/blocks/def/image-block.ts
new file mode 100644
index 00000000..8a37a918
--- /dev/null
+++ b/packages/ui/blocks/def/image-block.ts
@@ -0,0 +1,12 @@
+import type { Dimensions } from '../../types'
+import type Block from './block'
+
+interface ImageBlock extends Block {
+ blockType: 'image'
+ image?: string,
+ dim: Dimensions
+}
+
+export {
+ type ImageBlock as default,
+}
\ No newline at end of file
diff --git a/packages/ui/blocks/def/index.ts b/packages/ui/blocks/def/index.ts
new file mode 100644
index 00000000..76aaaa3d
--- /dev/null
+++ b/packages/ui/blocks/def/index.ts
@@ -0,0 +1,27 @@
+import type AccordianBlock from './accordian-block'
+import type BannerBlock from './banner-block'
+import type Block from './block'
+import type CardBlock from './card-block'
+import type CTABlock from './cta-block'
+import type ElementBlock from './element-block'
+import type GroupBlock from './group-block'
+import type HeadingBlock from './heading-block'
+import type ImageBlock from './image-block'
+import type VideoBlock from './video-block'
+import type SpaceBlock from './space-block'
+import type SpecialBlock from './special-block'
+
+export {
+ type AccordianBlock,
+ type BannerBlock,
+ type Block,
+ type CardBlock,
+ type CTABlock,
+ type ElementBlock,
+ type GroupBlock,
+ type HeadingBlock,
+ type ImageBlock,
+ type VideoBlock,
+ type SpaceBlock,
+ type SpecialBlock,
+}
\ No newline at end of file
diff --git a/packages/ui/blocks/def/space-block.ts b/packages/ui/blocks/def/space-block.ts
new file mode 100644
index 00000000..f389317b
--- /dev/null
+++ b/packages/ui/blocks/def/space-block.ts
@@ -0,0 +1,10 @@
+import type Block from './block'
+
+interface SpaceBlock extends Block {
+ blockType: 'space'
+ level?: number // default:0 = 1px div (plus any gap-y * 2), 1 = (plus any gap-y * 2), etc.
+}
+
+export {
+ type SpaceBlock as default
+}
diff --git a/packages/ui/blocks/def/special-block.ts b/packages/ui/blocks/def/special-block.ts
new file mode 100644
index 00000000..f90ce822
--- /dev/null
+++ b/packages/ui/blocks/def/special-block.ts
@@ -0,0 +1,11 @@
+import type Block from './block'
+
+interface SpecialBlock extends Block {
+ blockType: 'special'
+ typeId: string
+ options: any
+}
+
+export {
+ type SpecialBlock as default
+}
diff --git a/packages/ui/blocks/def/video-block.ts b/packages/ui/blocks/def/video-block.ts
new file mode 100644
index 00000000..269bb315
--- /dev/null
+++ b/packages/ui/blocks/def/video-block.ts
@@ -0,0 +1,27 @@
+import type { TShirtDimensions } from '../../types'
+import type Block from './block'
+
+interface VideoBlock extends Block {
+ blockType: 'video'
+ videoProps?: any // For example,
+ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ videoProps: {
+ autoPlay: true,
+ loop: true,
+ muted: true,
+ playsInline: true
+ },
+
+ Valueless props are boolean.
+ NOTE: Must be camalCase as per React conventions! (playsinline => playsInline)
+ ~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+ poster?: string
+ sources?: string[]
+ dim: TShirtDimensions
+ sizing?: any
+}
+
+export {
+ type VideoBlock as default,
+}
\ No newline at end of file
diff --git a/packages/ui/blocks/index.ts b/packages/ui/blocks/index.ts
new file mode 100644
index 00000000..c1768054
--- /dev/null
+++ b/packages/ui/blocks/index.ts
@@ -0,0 +1,2 @@
+export * from './def'
+export * from './components'
\ No newline at end of file
diff --git a/packages/ui/common/action-button.tsx b/packages/ui/common/action-button.tsx
new file mode 100644
index 00000000..c76db24d
--- /dev/null
+++ b/packages/ui/common/action-button.tsx
@@ -0,0 +1,46 @@
+import React from 'react'
+import dynamic from 'next/dynamic'
+
+import { cn } from '../util'
+
+import type { ButtonDef, ButtonModalDef } from '../types'
+import { ButtonSizes } from '../primitives/button'
+
+ // The DVC must be rendered client-side since it accesses the DOM directly.
+ // There is no need for a loading UI since the dialog only opens
+ // once it's been rendered and the user is already waiting.
+ // https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading
+const DynamicDVC = dynamic(() => (import('../primitives/dialog-video-controller')), {ssr: false})
+
+const ActionButton: React.FC<{
+ def: ButtonDef
+ size?: ButtonSizes
+ className?: string
+}> = ({
+ def,
+ size, // no default. overrides!
+ className=''
+}) => {
+ if (def.action.type === 'modal') {
+ const m = def.action.def as ButtonModalDef
+ const Modal = m.Comp
+ const sizeToSpread = size ? {size} : {}
+ return (
+
+
+
+ )
+ }
+ // no other types supported yet
+ return <>>
+}
+
+export default ActionButton
diff --git a/packages/ui/common/contact-dialog/contact-form.tsx b/packages/ui/common/contact-dialog/contact-form.tsx
new file mode 100644
index 00000000..87912de5
--- /dev/null
+++ b/packages/ui/common/contact-dialog/contact-form.tsx
@@ -0,0 +1,110 @@
+'use client'
+
+import React, { useTransition } from 'react'
+
+import { zodResolver } from '@hookform/resolvers/zod'
+import { useForm, SubmitHandler, type ControllerRenderProps } from 'react-hook-form'
+import * as z from 'zod'
+ // @ts-ignore
+import validator from 'validator'
+
+import { Loader2 } from 'lucide-react'
+
+import {
+ Button,
+ Input,
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '../../primitives'
+
+import type { ContactInfo, SubmitServerAction } from '../../types'
+
+const ValidationSchema = z.object({
+ email: z
+ .string()
+ .min(1, { message: "Email must be provided." })
+ .email("Invalid email."),
+ phone: z
+ .string()
+ .min(1, { message: "Telephone must be provided." })
+ .refine(validator.isMobilePhone, { message: "Invalid format." })
+})
+
+const ContactForm: React.FC<{
+ onSubmit: SubmitServerAction
+ enclosure: any
+}> = ({
+ onSubmit,
+ enclosure
+}) => {
+
+ const form = useForm({
+ resolver: zodResolver(ValidationSchema),
+ defaultValues: {
+ email: '',
+ phone: '',
+ },
+ })
+
+ const [isPending, startTransition] = useTransition()
+
+ const onFormSubmit: SubmitHandler = (data) => {
+ // https://github.com/orgs/react-hook-form/discussions/10757#discussioncomment-6672403
+ // @ts-ignore
+ startTransition(async () => {
+ await onSubmit(data, enclosure)
+ })
+ }
+
+ const MyFormItem: React.FC<{
+ field: ControllerRenderProps | ControllerRenderProps
+ placeholder: string
+ }> = ({
+ field,
+ placeholder
+ }) => (
+
+
+
+
+
+
+
+
+)
+
+ return (
+
+
+ )
+}
+
+export default ContactForm
diff --git a/packages/ui/common/contact-dialog/disclaimer.tsx b/packages/ui/common/contact-dialog/disclaimer.tsx
new file mode 100644
index 00000000..711b7817
--- /dev/null
+++ b/packages/ui/common/contact-dialog/disclaimer.tsx
@@ -0,0 +1,13 @@
+import React from 'react'
+
+const Disclaimer: React.FC = () => (
+
+ By entering your mobile number and submitting, you consent to receive text messages from Lux at the number provided.
+ Message and data rates may apply. Message frequency varies.
+ You can unsubscribe at any time by replying STOP.
+ View our
Privacy Policy and
Terms & conditions .
+
+)
+
+export default Disclaimer
+
diff --git a/packages/ui/common/contact-dialog/index.tsx b/packages/ui/common/contact-dialog/index.tsx
new file mode 100644
index 00000000..37ce637c
--- /dev/null
+++ b/packages/ui/common/contact-dialog/index.tsx
@@ -0,0 +1,48 @@
+'use client'
+import React from 'react'
+
+import type { ButtonModalProps} from '../../types'
+
+import {
+ Button,
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '../../primitives'
+
+import ContactForm from './contact-form'
+import Disclaimer from './disclaimer'
+
+const ContactDialog: React.FC = ({
+ open,
+ onOpenChange,
+ buttonText,
+ buttonProps,
+ title,
+ byline,
+ action,
+ actionEnclosure
+}) => (
+
+
+ {buttonText}
+
+
+
+ {title}
+ {byline && ({byline} )}
+
+
+
+
+)
+
+export default ContactDialog
diff --git a/packages/ui/common/copyright.tsx b/packages/ui/common/copyright.tsx
new file mode 100644
index 00000000..99d61fde
--- /dev/null
+++ b/packages/ui/common/copyright.tsx
@@ -0,0 +1,13 @@
+import React from 'react'
+
+const Copyright: React.FC = () => {
+
+ const year = new Date().getFullYear()
+ const yearString = (year > 2023) ? `${year} - 2023` : '2023'
+
+ return (
+ <>{`Copyright © ${yearString} Lux Partners Ltd. `} All rights reserved.>
+ )
+}
+
+export default Copyright
diff --git a/packages/ui/common/drawer-menu.tsx b/packages/ui/common/drawer-menu.tsx
new file mode 100644
index 00000000..a6f16c11
--- /dev/null
+++ b/packages/ui/common/drawer-menu.tsx
@@ -0,0 +1,43 @@
+'use client'
+import React, { type PropsWithChildren } from 'react'
+
+import { Sheet, SheetContent, SheetTrigger } from '../primitives/sheet'
+import * as Icons from './icons'
+
+const DrawerMenu: React.FC = ({
+ triggerIcon,
+ triggerProps,
+ children,
+ className=''
+}) => {
+
+ const [open, setOpen] = React.useState(false)
+
+ const onAction = () => { setOpen(false) }
+ const Icon = Icons[triggerIcon]
+
+ // https://stackoverflow.com/a/49052730/11645689
+ const updatedChildren = React.Children.map(
+ children,
+ (child) => (React.cloneElement(
+ child as any, { onAction }
+ ))
+ )
+
+ return (
+
+
+
+
+
+ {updatedChildren}
+
+
+ )
+}
+
+export default DrawerMenu
diff --git a/packages/ui/common/footer.tsx b/packages/ui/common/footer.tsx
new file mode 100644
index 00000000..7d1eed04
--- /dev/null
+++ b/packages/ui/common/footer.tsx
@@ -0,0 +1,53 @@
+import React from 'react'
+
+import { ButtonVariants } from '../primitives'
+import type { LinkDef, SiteConf } from '../types'
+import { Copyright, NavItems } from '../common'
+
+import Logo from './logo'
+
+const Footer: React.FC<{
+ conf: SiteConf,
+ className?: string,
+ noHorizPadding?: boolean
+}> = ({
+ conf,
+ className='',
+ noHorizPadding=false
+}) => (
+
+)
+
+export default Footer
diff --git a/packages/ui/common/header/index.tsx b/packages/ui/common/header/index.tsx
new file mode 100644
index 00000000..eeb0f44f
--- /dev/null
+++ b/packages/ui/common/header/index.tsx
@@ -0,0 +1,59 @@
+import React from 'react'
+
+import type SiteConf from '../../types/site-conf'
+
+import Logo from '../logo'
+import { ButtonVariants } from '../../primitives'
+import { NavItems, LinkElement, DrawerMenu } from '../../common'
+import MobileNav from './mobile-nav'
+
+const Header: React.FC<{
+ conf: SiteConf
+}> = ({
+ conf
+}) => (
+
+)
+
+export default Header
diff --git a/packages/ui/common/header/mobile-nav.tsx b/packages/ui/common/header/mobile-nav.tsx
new file mode 100644
index 00000000..16599ac4
--- /dev/null
+++ b/packages/ui/common/header/mobile-nav.tsx
@@ -0,0 +1,38 @@
+'use client'
+import React from 'react'
+
+import { LinkElement } from '../../common'
+import { ButtonVariants } from '../../primitives'
+import { SiteConf } from '../../types'
+
+const MobileNav: React.FC<{
+ conf: SiteConf
+ itemVariant?: ButtonVariants
+ className?: string
+ itemClassName?: string
+ onAction?: () => void // for close functionality
+}> = ({
+ conf,
+ onAction,
+ className='',
+ itemClassName='',
+ itemVariant
+}) => (
+ conf.mainNav.full.length ? (
+
+ {conf.mainNav.full.map((el, index) => (
+
+ ))}
+
+ )
+ : null
+)
+
+export default MobileNav
diff --git a/packages/ui/common/header/theme-toggle.tsx b/packages/ui/common/header/theme-toggle.tsx
new file mode 100644
index 00000000..1385aa02
--- /dev/null
+++ b/packages/ui/common/header/theme-toggle.tsx
@@ -0,0 +1,26 @@
+'use client'
+
+import React from 'react'
+import { Moon, Sun } from 'lucide-react'
+import { useTheme } from 'next-themes'
+
+import { Button } from '../../primitives'
+
+const ThemeToggle: React.FC = () => {
+
+ const { setTheme, theme } = useTheme()
+
+ return (
+ {setTheme(theme === 'light' ? 'dark' : 'light')}}
+ >
+
+
+ Toggle theme
+
+ )
+}
+
+export default ThemeToggle
diff --git a/packages/ui/common/icons/github.tsx b/packages/ui/common/icons/github.tsx
new file mode 100644
index 00000000..cfdeaa92
--- /dev/null
+++ b/packages/ui/common/icons/github.tsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import { type LucideProps } from 'lucide-react'
+
+
+const GitHub: React.FC = (props: LucideProps) => (
+
+
+
+)
+
+export default GitHub
diff --git a/packages/ui/common/icons/index.tsx b/packages/ui/common/icons/index.tsx
new file mode 100644
index 00000000..6aa5404c
--- /dev/null
+++ b/packages/ui/common/icons/index.tsx
@@ -0,0 +1,34 @@
+import {
+ Moon as moon,
+ SunMedium as sun,
+ Menu as burger,
+ ArrowUpRight as linkOut,
+ type XIcon as LucideIcon,
+} from 'lucide-react'
+
+import gitHub from './github'
+import logo from './lux-logo'
+import youtube from './youtube-logo'
+import secureDelivery from './secure-delivery'
+
+import {
+ type SocialIconProps,
+ default as SocialIcon
+} from './social-icon'
+
+export { type LucideIcon as Icon, type SocialIconProps }
+
+
+export {
+ sun,
+ moon,
+ logo,
+ burger,
+ gitHub,
+ linkOut,
+ youtube,
+ secureDelivery,
+ SocialIcon,
+}
+
+
diff --git a/packages/ui/common/icons/lux-logo.tsx b/packages/ui/common/icons/lux-logo.tsx
new file mode 100644
index 00000000..10da7284
--- /dev/null
+++ b/packages/ui/common/icons/lux-logo.tsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import { type LucideProps } from 'lucide-react'
+
+const LuxLogo: React.FC = (props: LucideProps) => (
+
+
+
+)
+
+export default LuxLogo
diff --git a/packages/ui/common/icons/secure-delivery.tsx b/packages/ui/common/icons/secure-delivery.tsx
new file mode 100644
index 00000000..c0467056
--- /dev/null
+++ b/packages/ui/common/icons/secure-delivery.tsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import { type LucideProps } from 'lucide-react'
+
+// width="52" height="36"
+const SecureDelivery: React.FC = (props: LucideProps) => (
+
+
+
+
+
+)
+
+export default SecureDelivery
diff --git a/packages/ui/common/icons/social-icon.tsx b/packages/ui/common/icons/social-icon.tsx
new file mode 100644
index 00000000..627dbe44
--- /dev/null
+++ b/packages/ui/common/icons/social-icon.tsx
@@ -0,0 +1,35 @@
+import React from 'react'
+
+import { SocialIcon as BaseSocialIcon } from 'react-social-icons'
+
+import { cn } from '../../util'
+import '../../style/social-svg.css'
+
+interface SocialIconProps {
+ // one of these: https://github.com/couetilc/react-social-icons/tree/main/db
+ network: string
+ size: number
+ className?: string
+}
+
+const SocialIcon: React.FC = ({
+ network,
+ size,
+ className = ''
+}) => (
+
+)
+
+export {
+ type SocialIconProps,
+ SocialIcon as default
+}
diff --git a/packages/ui/common/icons/youtube-logo.tsx b/packages/ui/common/icons/youtube-logo.tsx
new file mode 100644
index 00000000..9a599b04
--- /dev/null
+++ b/packages/ui/common/icons/youtube-logo.tsx
@@ -0,0 +1,59 @@
+import React from 'react'
+import { type LucideProps } from 'lucide-react'
+
+const YouTubeLogo: React.FC = (props: LucideProps) => (
+
+
+
+
+
+
+)
+
+export default YouTubeLogo
diff --git a/packages/ui/common/index.ts b/packages/ui/common/index.ts
new file mode 100644
index 00000000..c16fbe4f
--- /dev/null
+++ b/packages/ui/common/index.ts
@@ -0,0 +1,16 @@
+export * as Icons from './icons'
+export { default as ContactDialog } from './contact-dialog'
+export { default as Logo } from './logo'
+export { default as Main } from './main'
+export { default as Footer } from './footer'
+export { default as Header } from './header'
+export { default as MiniChart } from './mini-chart'
+export { default as ActionButton } from './action-button'
+export { default as Copyright} from './copyright'
+export { default as DrawerMenu} from './drawer-menu'
+export { default as LinkElement} from './link-element'
+export { default as NavItems} from './nav-items'
+export { default as YouTubeEmbed} from './youtube-embed'
+
+
+
diff --git a/packages/ui/common/link-element.tsx b/packages/ui/common/link-element.tsx
new file mode 100644
index 00000000..5d17686d
--- /dev/null
+++ b/packages/ui/common/link-element.tsx
@@ -0,0 +1,70 @@
+import React from 'react'
+import Link from 'next/link'
+
+import type { LinkDef, Icon } from '../types'
+import { buttonVariants, type ButtonSizes, type ButtonVariants } from '../primitives/button'
+import { cn } from '../util'
+
+const LinkElement: React.FC<{
+ def: LinkDef,
+ variant? : ButtonVariants
+ size?: ButtonSizes
+ onClick?: () => void // for UI changes in addition to link (eg, close menu)
+ className?: string,
+ icon?: Icon // for title area
+ iconAfter?: boolean
+}> = ({
+ def,
+ size = 'default',
+ onClick,
+ variant,
+ className = '',
+ icon, // override
+ iconAfter // override
+} ) => {
+
+ const {
+ href,
+ external,
+ newTab,
+ variant: defVariant,
+ size: defSize,
+ title
+ } = def
+
+ const toSpread = {
+ ...((href) ? { href } : { href: '#'}),
+ ...((external) ? {
+ rel: 'noreferrer',
+ // As per comments in LinkDef
+ target: (newTab !== undefined && (newTab === false)) ? '_self' : '_blank'
+ } : {
+ target: (newTab !== undefined && (newTab === true)) ? '_blank' : '_self'
+ }),
+ ...(onClick ? { onClick } : {})
+ }
+
+ const iicon = (icon) ? icon : (def.icon) ? def.icon : undefined
+ const iiconAfter = (iconAfter) ? iconAfter : (def.iconAfter) ? def.iconAfter : false
+
+ return (
+
+ {iicon && !iiconAfter && ({iicon as React.ReactNode}
)}
+ {title}
+ {iicon && iiconAfter && ({iicon as React.ReactNode}
)}
+
+ )
+}
+
+export default LinkElement
diff --git a/packages/ui/common/logo.tsx b/packages/ui/common/logo.tsx
new file mode 100644
index 00000000..eedfc297
--- /dev/null
+++ b/packages/ui/common/logo.tsx
@@ -0,0 +1,52 @@
+import React from 'react'
+import Link from 'next/link'
+
+import { type TShirtSize } from '../types'
+import { cn } from '../util'
+import { Icons } from '../common'
+
+const Logo: React.FC<{
+ size: TShirtSize
+ logoOnly?: boolean
+ href?: string
+ className?: string
+}> = ({
+ size,
+ href='/',
+ className='',
+ logoOnly=false
+}) => {
+ let classes: any = {}
+ const toAdd = (logoOnly) ? {
+ span: ' hidden',
+ icon: ' mr-r'
+ } : {
+ span: '',
+ icon: ''
+ }
+ if (size === 'lg') {
+ classes.icon = 'h-10 w-10 mr-4 color-inherit' + toAdd.icon
+ classes.span = 'text-3xl' + toAdd.span
+ }
+ // match lux.network
+ else if (size === 'md') {
+ classes.icon = 'h-[32px] w-[32px] mr-[12px] color-inherit' + toAdd.icon
+ classes.span = 'text-[26px]/[26px] tracking-tighter' + toAdd.span
+ }
+ else {
+ classes.icon = 'h-6 w-6 mr-2 color-inherit' + toAdd.icon
+ classes.span = 'text-lg' + toAdd.span
+ }
+
+ const spanClasses = 'inline-block font-bold font-heading ' + classes.span
+ const linkClasses = 'flex items-center text-primary hover:text-primary-hover ' + className
+
+ return (
+
+
+ LUX
+
+ )
+}
+
+export default Logo
diff --git a/packages/ui/common/main.tsx b/packages/ui/common/main.tsx
new file mode 100644
index 00000000..a752f3af
--- /dev/null
+++ b/packages/ui/common/main.tsx
@@ -0,0 +1,21 @@
+import React, { type PropsWithChildren } from 'react'
+import { cn } from '../util'
+
+const c = 'max-w-screen-2xl 2xl:w-[1500px] lg:mx-auto ' +
+ 'flex flex-col justify-start items-stretch ' +
+ 'p-4 md:px-6 lg:px-8 '
+
+const Main: React.FC<
+ PropsWithChildren & {
+ className?: string
+ }
+> = ({
+ children,
+ className='',
+}) => (
+
+ {children}
+
+)
+
+export default Main
diff --git a/packages/ui/common/mini-chart/index.tsx b/packages/ui/common/mini-chart/index.tsx
new file mode 100644
index 00000000..540273dc
--- /dev/null
+++ b/packages/ui/common/mini-chart/index.tsx
@@ -0,0 +1,8 @@
+import MiniChart from './wrapper'
+// import { type MiniChartProps, type MiniChartDateRanges } from './mini-chart-props'
+
+export {
+ MiniChart as default,
+ // type MiniChartProps,
+ // type MiniChartDateRanges
+}
\ No newline at end of file
diff --git a/packages/ui/common/mini-chart/mini-chart-props.ts b/packages/ui/common/mini-chart/mini-chart-props.ts
new file mode 100644
index 00000000..a86cb177
--- /dev/null
+++ b/packages/ui/common/mini-chart/mini-chart-props.ts
@@ -0,0 +1,44 @@
+import { CSSProperties } from 'react'
+
+ // https://www.typescriptlang.org/docs/handbook/enums.html#enums-at-runtime
+const enum MiniChartDateRangesValues {
+ '1D',
+ '1M',
+ '3M',
+ '12M',
+ '60M',
+ 'ALL',
+}
+
+type MiniChartDateRanges = keyof typeof MiniChartDateRangesValues
+
+interface MiniChartBaseProps {
+ symbol: string
+ exchange?: string
+ dateRange: MiniChartDateRanges
+ noTimeScale?: boolean
+ width?: number | string
+ height?: number | string
+ locale?: string
+ colorTheme?: "light" | "dark"
+ // @ts-ignore // TODO
+ lineColor?: CSSProperties["color"]
+ // @ts-ignore // TODO
+ topGradientColor?: CSSProperties["color"]
+ // @ts-ignore // TODO
+ bottomGradientColor?: CSSProperties["color"]
+ isTransparent?: boolean
+ autosize?: boolean
+ chartOnly?: boolean
+ largeChartUrl?: string
+}
+
+type MiniChartProps = {
+ widgetProps: MiniChartBaseProps
+ containerStyles?: CSSProperties
+}
+
+export {
+ type MiniChartProps,
+ type MiniChartDateRanges
+}
\ No newline at end of file
diff --git a/packages/ui/common/mini-chart/mini-chart.tsx b/packages/ui/common/mini-chart/mini-chart.tsx
new file mode 100644
index 00000000..6cccb306
--- /dev/null
+++ b/packages/ui/common/mini-chart/mini-chart.tsx
@@ -0,0 +1,76 @@
+'use client'
+
+import React, { useEffect, useRef } from 'react'
+import { type MiniChartProps } from './mini-chart-props'
+
+const MiniChart: React.FC = ({
+ widgetProps,
+ containerStyles = {}
+}) => {
+
+ const {
+ symbol,
+ exchange,
+ lineColor,
+ topGradientColor,
+ bottomGradientColor,
+ width,
+ height,
+ autosize,
+ locale,
+ ...rest
+ } = widgetProps
+
+ const symbolString = (exchange) ? `${exchange}:${symbol}` : symbol
+
+ const containerRef = useRef(null)
+
+ useEffect(() => {
+ if (!containerRef.current) {
+ return
+ }
+ const script = document.createElement('script')
+ script.type = 'text/javascript'
+ script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-mini-symbol-overview.js'
+ script.async = true
+ script.onload = async () => {
+ const iframe = containerRef.current?.querySelector('iframe')
+ if (iframe && iframe instanceof Element) iframe.style.colorScheme = 'normal'
+
+ const copyDiv = document.querySelector('.tradingview-widget-copyright')
+ if (copyDiv) {
+ setTimeout(() => {
+ copyDiv.classList.remove('invisible')
+ }, 1200) // from experimentation
+ }
+ }
+ const config = JSON.stringify({
+ symbol: symbolString,
+ width: autosize ? '100%' : (width || 350),
+ height: autosize ? '100%' : (height || 220),
+ locale: locale ?? 'en',
+ isTransparent: 'true',
+ trendLineColor: lineColor || 'rgba(41, 98, 255, 1)',
+ underLineColor: topGradientColor || 'rgba(41, 98, 255, 0.3)',
+ underLineBottomColor: bottomGradientColor || 'rgba(41, 98, 255, 0)',
+ ...rest,
+ })
+ script.innerHTML = config
+ containerRef.current.appendChild(script)
+ return () => {
+ while (containerRef.current?.firstChild) {
+ containerRef.current.removeChild(containerRef.current.firstChild)
+ }
+ }
+ }, [ JSON.stringify(widgetProps) ])
+
+ return (<>
+
+
+ >)
+}
+
+export default MiniChart
diff --git a/packages/ui/common/mini-chart/wrapper.tsx b/packages/ui/common/mini-chart/wrapper.tsx
new file mode 100644
index 00000000..db061a40
--- /dev/null
+++ b/packages/ui/common/mini-chart/wrapper.tsx
@@ -0,0 +1,23 @@
+import React from 'react'
+
+import BaseChart from './mini-chart'
+
+const Wrapper: React.FC<{
+ symbol: string,
+ exchange?: string
+}> = ({
+ symbol,
+ exchange
+}) => (
+
+)
+
+export default Wrapper
diff --git a/packages/ui/common/nav-items.tsx b/packages/ui/common/nav-items.tsx
new file mode 100644
index 00000000..78ccbb89
--- /dev/null
+++ b/packages/ui/common/nav-items.tsx
@@ -0,0 +1,65 @@
+import React from 'react'
+
+import type { LinkDef } from '../types'
+import { ButtonVariants } from '../primitives/button'
+import { cn } from '../util'
+
+import LinkElement from './link-element'
+
+const NavItems: React.FC<{
+ items: LinkDef[]
+ className?: string,
+ as?: React.ElementType
+ itemClassName?: string,
+ itemClassNameFromVariant?: (variant: ButtonVariants) => string
+ currentAs?: string
+}> = ({
+ items,
+ className='',
+ itemClassName='',
+ as : Tag='nav',
+ itemClassNameFromVariant,
+ currentAs
+}) => (
+ items.length ? (
+
+ {items.map((item, index) => {
+
+ const variant = item.variant ?? 'link'
+ let extraClasses = ''
+ // note that linkFG (or any other variant of 'link')
+ // will not get assigned these classes,
+ // and will remain styles is 'foreground' (hence the name)
+ if (variant === 'link') {
+
+ extraClasses+= ' text-nav hover:text-nav-hover'
+ if (currentAs && currentAs === item.href) {
+ extraClasses += ' text-nav-current'
+ }
+ }
+ else {
+ extraClasses+= ' min-w-0'
+ }
+ if (currentAs && currentAs === item.href) {
+ extraClasses += ' pointer-events-none'
+ }
+
+ return (
+
+ )
+ })}
+
+ )
+ : null
+ )
+
+ export default NavItems
diff --git a/packages/ui/common/typography-test.mdx b/packages/ui/common/typography-test.mdx
new file mode 100644
index 00000000..779f86ad
--- /dev/null
+++ b/packages/ui/common/typography-test.mdx
@@ -0,0 +1,36 @@
+import Link from '@luxdefi/ui/common/link-element'
+
+###### In a galaxy far, far away
+##### Join the global rush to own silver
+#### And the global rush to gold
+### As well as the rush to crypto
+## THE SILVER RUSH IS HERE
+# THE GOLD RUSH IS HERE
+
+
+*Lorem* ipsum dolor sit amet, **consectetur** adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco
+laboris nisi ut aliquip ex ea commodo consequat.
+
+1) Duis aute irure
+2) dolor in reprehenderit
+3) in voluptate velit esse
+
+Cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
+
+- sunt in culpa
+- qui officia deserunt
+- mollit anim id est laborum.
+
+Lorem ipsum dolor sit [amet]('/'), adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+
+
diff --git a/packages/ui/common/youtube-embed.tsx b/packages/ui/common/youtube-embed.tsx
new file mode 100644
index 00000000..d3ce8573
--- /dev/null
+++ b/packages/ui/common/youtube-embed.tsx
@@ -0,0 +1,83 @@
+'use client'
+
+import { useState } from 'react'
+import Image from 'next/image'
+
+import * as Icons from './icons'
+
+// Concept based on https://www.youtube.com/watch?v=lLqRchtjN00
+// (https://github.com/a-trost/fableton)
+
+const YouTubeEmbed: React.FC<{
+ youtubeID: string
+ width: number
+ height: number
+ buttonSize?: number
+ timeAt?: string // '5s'
+ title?: string
+ caption?: string
+ className?: string
+}> = ({
+ youtubeID,
+ width,
+ height,
+ buttonSize=100,
+ timeAt,
+ title,
+ caption,
+ className=''
+}) => {
+
+ const [showVideo, setShowVideo] = useState(false)
+
+ // re autoplay: https://stackoverflow.com/a/45610557/11645689
+ return (
+
+ {showVideo ? (
+
+ ) : (
+
setShowVideo(true)}
+ className='relative aspect-[16/9] w-full'
+ aria-label={`Play video ${title}`}
+ >
+
+
+
+
+ {caption && {caption}
}
+
+ )}
+
+ )
+}
+
+export default YouTubeEmbed
diff --git a/packages/ui/conf/footer.tsx b/packages/ui/conf/footer.tsx
new file mode 100644
index 00000000..06c2eb85
--- /dev/null
+++ b/packages/ui/conf/footer.tsx
@@ -0,0 +1,164 @@
+import { Icons } from '../common'
+import { LinkDef } from '../types'
+
+const SOC_ICON_SIZE = 18
+
+export default [
+ [
+ {
+ title: 'Market',
+ href: "https://app.lux.market/",
+ external: true,
+ newTab: false,
+ variant: 'linkFG'
+ },
+ {
+ title: 'Lux Silver',
+ href: '/silver',
+ },
+ {
+ title: 'Lux Gold',
+ href: '/gold',
+ },
+ {
+ title: 'Lux Coin',
+ href: '/coin',
+ },
+ /*{
+ title: 'Lux Uranium',
+ href: '/uranium',
+ }*/,
+ {
+ title: 'Lux Credit',
+ href: '/credit',
+ },
+ {
+ title: 'Lux Validator',
+ href: '/validator',
+ },
+ {
+ title: 'Lux Pass',
+ href: '/pass',
+ },
+ ],
+ [
+ {
+ title: 'Network',
+ href: "https://lux.network/",
+ variant: 'linkFG'
+ },
+ {
+ title: 'Apply for Beta',
+ href: 'https://apply.lux.network/',
+ external: true,
+ newTab: false
+ },
+ {
+ title: 'Whitepapers',
+ href: 'https://docs.lux.network/intro/about',
+ },
+ {
+ title: 'Github',
+ href: 'https://github.com/luxdefi',
+ },
+ ],
+ [
+ {
+ title: 'Partners',
+ href: "https://lux.partners/",
+ variant: 'linkFG',
+ external: true
+ },
+ {
+ title: 'About',
+ href: 'https://lux.partners',
+ external: true,
+ newTab: false,
+ },
+ {
+ title: 'Partner with Lux',
+ href: 'https://apply.lux.partners/',
+ external: true
+ },
+ {
+ title: 'Lux Fund',
+ href: 'https://lux.fund',
+ newTab: false,
+ external: true
+ },
+ {
+ title: 'Lux Support',
+ href: 'mailto:support@lux.partners?subject=%E2%96%BC%20Lux%20Support',
+ external: true
+ },
+ {
+ title: 'Careers',
+ href: 'https://docs.lux.network/about/v/lux-job-listings/',
+ newTab: false,
+ external: true
+ },
+ {
+ title: 'Press Kit',
+ href: 'https://drive.google.com/drive/folders/14OJtKLVakGY6883XO9yGbiHtlFxQUUm5?usp=share_link',
+ external: true
+ },
+ ],
+ [
+ {
+ title: 'Community',
+ variant: 'linkFG',
+ },
+ {
+ title: 'Discord',
+ href: 'https://discord.gg/luxdefi',
+ external: true,
+ icon:
+ },
+ {
+ title: 'Telegram',
+ href: 'https://t.me/luxdefi',
+ external: true,
+ icon:
+ },
+ {
+ title: 'X (Twitter)',
+ href: 'https://twitter.com/luxdefi',
+ external: true,
+ icon:
+ },
+ {
+ title: 'Facebook',
+ href: 'https://facebook.com/luxdefi',
+ external: true,
+ icon:
+ },
+ {
+ title: 'LinkedIn',
+ href: 'https://linkedin.com/company/luxdefi',
+ external: true,
+ icon:
+ },
+ {
+ title: 'Instagram',
+ href: 'https://instagram.com/luxdefi',
+ external: true,
+ icon:
+ },
+ ],
+ [
+ {
+ title: 'Legal',
+ variant: 'linkFG',
+ },
+ {
+ title: 'Terms and Conditions',
+ href: '/assets/standard-docs/LUX-NFT-Terms-and-Conditions.pdf',
+ newTab: true,
+ },
+ {
+ title: 'Privacy Policy',
+ href: '/assets/standard-docs/LUX-Privacy-Policy.pdf',
+ newTab: true,
+ },
+ ],
+] as LinkDef[][]
\ No newline at end of file
diff --git a/packages/ui/conf/main-nav.ts b/packages/ui/conf/main-nav.ts
new file mode 100644
index 00000000..6aa9153b
--- /dev/null
+++ b/packages/ui/conf/main-nav.ts
@@ -0,0 +1,58 @@
+import type { LinkDef } from '../types'
+
+const short = [
+ {
+ title: "AI",
+ href: "https://lux.chat",
+ external: true,
+ newTab: false,
+ },
+ {
+ title: "Credit",
+ href: "https://lux.credit",
+ external: true,
+ newTab: false,
+ },
+ {
+ title: "Finance",
+ href: "https://lux.finance",
+ external: true,
+ newTab: false,
+ },
+ {
+ title: "Market",
+ href: "https://lux.market",
+ external: true,
+ newTab: false,
+ },
+ {
+ title: "Network",
+ href: "https://lux.network",
+ external: true,
+ newTab: false,
+ },
+ {
+ title: "Team",
+ href: "https://lux.partners",
+ external: true,
+ newTab: false,
+ },
+] as LinkDef[]
+
+const aux = [
+ {
+ title: "Enter App",
+ href: "https://app.lux.market",
+ newTab: false,
+ external: true,
+ variant: 'primary',
+ },
+] as LinkDef[]
+
+const full = [...short, ...aux]
+
+export {
+ short,
+ aux,
+ full
+}
diff --git a/packages/ui/context-providers/theme-provider.tsx b/packages/ui/context-providers/theme-provider.tsx
new file mode 100644
index 00000000..23b951b2
--- /dev/null
+++ b/packages/ui/context-providers/theme-provider.tsx
@@ -0,0 +1,11 @@
+"use client"
+
+import React from "react"
+import { ThemeProvider as NextThemesProvider } from "next-themes"
+import { type ThemeProviderProps } from "next-themes/dist/types"
+
+const ThemeProvider: React.FC = ({ children, ...props }) => (
+ {children}
+)
+
+export default ThemeProvider
diff --git a/packages/ui/index.ts b/packages/ui/index.ts
new file mode 100644
index 00000000..8c3682eb
--- /dev/null
+++ b/packages/ui/index.ts
@@ -0,0 +1,21 @@
+export * from './util/index'
+export * from './util/specifier'
+
+export * from './common'
+
+export {
+ type ButtonDef,
+ type Dimensions,
+ type SubmitServerAction,
+ type ButtonModalProps,
+ type ButtonModalDef,
+ type LinkDef,
+ type Icon,
+ type TShirtSize,
+ type Breakpoint,
+ type TShirtDimensions,
+ Breakpoints
+} from './types'
+
+export * from './primitives'
+export * from './next'
\ No newline at end of file
diff --git a/packages/ui/next/determine-device-middleware.ts b/packages/ui/next/determine-device-middleware.ts
new file mode 100644
index 00000000..fb451003
--- /dev/null
+++ b/packages/ui/next/determine-device-middleware.ts
@@ -0,0 +1,13 @@
+import { NextRequest, NextResponse, userAgent } from 'next/server'
+import { getSelectorsByUserAgent } from 'react-device-detect'
+
+export const determineDeviceMiddleware = async (request: NextRequest) => {
+
+ const ua = userAgent(request)
+ const { isMobileOnly, isTablet, isDesktop } = getSelectorsByUserAgent(ua.ua)
+ const agent = isMobileOnly ? 'phone' : (isTablet ? 'tablet' : (isDesktop ? 'desktop' : 'unknown'))
+ const { nextUrl: url } = request
+ //console.log(`\n=== from ${url.href} on a *${agent && agent.toUpperCase()}* device. ===\n`)
+ url.searchParams.set('agent', agent)
+ return NextResponse.rewrite(url)
+}
diff --git a/packages/ui/next/index.ts b/packages/ui/next/index.ts
new file mode 100644
index 00000000..9c8a9c41
--- /dev/null
+++ b/packages/ui/next/index.ts
@@ -0,0 +1,11 @@
+import { determineDeviceMiddleware } from './determine-device-middleware'
+import NotFoundMDX from './not-found-content.mdx'
+import NotFound from './not-found'
+import RootLayout from './root-layout'
+
+export {
+ determineDeviceMiddleware,
+ NotFoundMDX,
+ NotFound,
+ RootLayout
+}
diff --git a/packages/ui/next/not-found-content.mdx b/packages/ui/next/not-found-content.mdx
new file mode 100644
index 00000000..8e97dd80
--- /dev/null
+++ b/packages/ui/next/not-found-content.mdx
@@ -0,0 +1,4 @@
+### ...Huh?
+#### Sorry, we're fresh out of those.
+
+#### ...try something from the [Home Page](/)?
\ No newline at end of file
diff --git a/packages/ui/next/not-found.tsx b/packages/ui/next/not-found.tsx
new file mode 100644
index 00000000..ca62eef5
--- /dev/null
+++ b/packages/ui/next/not-found.tsx
@@ -0,0 +1,23 @@
+import React from 'react'
+
+import Main from '../common/main'
+import Footer from '../common/footer'
+import { ApplyTypography } from '../primitives'
+
+import NotFoundMDX from './not-found-content.mdx'
+import { SiteConf } from '../types'
+
+const NotFound: React.FC<{
+ conf: SiteConf
+}> = ({
+ conf
+}) => (<>
+
+
+
+
+
+
+>)
+
+export default NotFound
diff --git a/packages/ui/next/root-layout.tsx b/packages/ui/next/root-layout.tsx
new file mode 100644
index 00000000..2a7e43a3
--- /dev/null
+++ b/packages/ui/next/root-layout.tsx
@@ -0,0 +1,100 @@
+import React, { type PropsWithChildren } from 'react'
+import { Metadata } from 'next'
+
+import { inter, drukTextWide } from '../style/nextFonts'
+import Header from '../common/header'
+import SiteConf from '../types/site-conf'
+
+const getMetadata = (conf: SiteConf): Metadata => ({
+
+ title: {
+ default: conf.title,
+ template: conf.template,
+ },
+ description: conf.desc,
+
+ // Generated from a 512 original create in GIMP
+ // at https://favicon.io/favicon-converter/
+
+ // Next API is poorly documented.
+ // cf: Next.js repo: packages/next/src/lib/metadata/types/metadata-interface.ts
+ // and metadata-types.ts
+ icons: [
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '16x16',
+ url: '/assets/icon/favicon-16x16.png'
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '32x32',
+ url: '/assets/icon/favicon-32x32.png'
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '192x192',
+ url: '/assets/icon/android-chrome-192x192.png'
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '512x512',
+ url: '/assets/icon/android-chrome-512x512.png'
+ },
+ {
+ rel: 'apple-touch-icon',
+ type: 'image/png',
+ sizes: '180x180',
+ url: '/assets/icon/apple-touch-icon.png'
+ },
+ ]
+})
+
+// Next 14: https://nextjs.org/docs/app/building-your-application/upgrading/codemods#use-viewport-export
+const viewport = {
+ themeColor: [
+ { media: '(prefers-color-scheme: light)', color: 'white' },
+ { media: '(prefers-color-scheme: dark)', color: 'black' },
+ ],
+}
+
+/*
+ These '.variable' fields are actually autogenerate css classnames that *define* the actual
+ css variables ('--foo-var') that one asks for in the utility functions.
+ They are what make them available in their scope, so this MUST
+ be done like this for the tailwind font classes to work.
+
+ (not to be confused with the css var itself. This field should be named something else!)
+*/
+
+// overflow-y-hidden overflow-x-hidden ,-- cannot have these on body tag for scroll-snap on iOS
+const bodyClasses =
+ 'bg-background fg-foreground ' +
+ `${inter.variable} ${drukTextWide.variable} font-sans`
+
+ // re : https://stackoverflow.com/a/75716588/11645689
+const RootLayout: React.FC<
+ PropsWithChildren & { conf: SiteConf }
+> = ({
+ conf,
+ children,
+}) => (
+
+
+
+
+
+
+ {children}
+
+
+)
+
+export {
+ RootLayout as default,
+ getMetadata,
+ viewport
+}
diff --git a/packages/ui/package.json b/packages/ui/package.json
new file mode 100644
index 00000000..72e7a197
--- /dev/null
+++ b/packages/ui/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "@luxdefi/ui",
+ "version": "0.1.0",
+ "description": "Library that contains shared UI primitives, styles, and core types",
+ "main": "dist/index.js",
+ "scripts": {
+ "build": "rm -rf dist && tsc",
+ "tc": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@hookform/resolvers": "^3.3.2",
+ "@radix-ui/react-accordion": "^1.1.2",
+ "@radix-ui/react-dialog": "^1.0.5",
+ "@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-toast": "^1.1.5",
+ "@tailwindcss/container-queries": "^0.1.1",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.0",
+ "lodash.castarray": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.merge": "^4.6.2",
+ "lucide-react": "^0.307.0",
+ "markdown-to-jsx": "^7.3.2",
+ "next": "^14.0.4",
+ "next-themes": "^0.2.1",
+ "postcss-selector-parser": "^6.0.13",
+ "react": "^18.2.0",
+ "react-device-detect": "^2.2.3",
+ "react-dom": "^18.2.0",
+ "react-hook-form": "^7.47.0",
+ "react-social-icons": "^6.4.0",
+ "tailwind-merge": "^2.2.0",
+ "tailwindcss": "^3.4.1",
+ "tailwindcss-animate": "^1.0.6",
+ "tailwindcss-interaction-media": "^0.1.0",
+ "validator": "^13.11.0",
+ "zod": "^3.22.4"
+ },
+ "devDependencies": {
+ "@types/mdx": "^2.0.9",
+ "@types/node": "^20.10.7",
+ "@types/react": "^18.2.47",
+ "@types/react-dom": "^18.2.18",
+ "autoprefixer": "^10.4.16",
+ "postcss": "^8.4.33",
+ "postcss-import": "^16.0.0",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/ui/primitives/accordion.tsx b/packages/ui/primitives/accordion.tsx
new file mode 100644
index 00000000..fed82a1d
--- /dev/null
+++ b/packages/ui/primitives/accordion.tsx
@@ -0,0 +1,61 @@
+// @ts-nocheck
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "../util"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/packages/ui/primitives/apply-typography.tsx b/packages/ui/primitives/apply-typography.tsx
new file mode 100644
index 00000000..a88a443e
--- /dev/null
+++ b/packages/ui/primitives/apply-typography.tsx
@@ -0,0 +1,51 @@
+import React, { PropsWithChildren } from 'react'
+
+import { cn } from '../util'
+
+type TypographySize = 'responsive' | 'sm' | 'base' | 'lg' // if t-shirt size, do *not* be responsive
+
+const ApplyTypography: React.FC<
+ PropsWithChildren & {
+ className?: string,
+ asTag?: 'div' | 'section' | 'nav' | 'main' | 'article',
+ size?: TypographySize
+ }
+> = ({
+ children,
+ className='',
+ asTag='div',
+ size='responsive'
+}) => {
+
+ // responsive version by default
+ let typoClasses =
+ 'typography gap-3 ' +
+ 'xs:typography-sm ' +
+ 'sm:typography sm:gap-4 ' +
+ 'lg:typography-lg lg:gap-5 ' +
+ 'typography-headings:font-heading ' // only effects h1-h3 (in plugin)
+
+ switch (size) {
+ case 'sm': {
+ typoClasses = 'typography typography-sm gap-3 typography-headings:font-heading typography-p:text-sm '
+ } break
+ case 'base': {
+ typoClasses = 'typography gap-4 typography-headings:font-heading '
+ } break
+ case 'lg': {
+ typoClasses = 'typography typography-lg gap-5 typography-headings:font-heading typography-p:text-lg '
+ } break
+ }
+
+ const Tag = asTag
+ return (
+
+ {children}
+
+ )
+}
+
+export {
+ type TypographySize,
+ ApplyTypography as default
+}
diff --git a/packages/ui/primitives/button.tsx b/packages/ui/primitives/button.tsx
new file mode 100644
index 00000000..d0cb73a7
--- /dev/null
+++ b/packages/ui/primitives/button.tsx
@@ -0,0 +1,72 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "../util"
+
+const variant = {
+ primary: "bg-primary text-primary-fg hover:bg-primary-hover font-heading whitespace-nowrap not-typography",
+ secondary: "bg-secondary text-secondary-fg hover:bg-secondary/80 font-heading whitespace-nowrap not-typography",
+ outline: "text-foreground border border-primary hover:bg-level-1 hover:text-accent font-heading whitespace-nowrap not-typography",
+ destructive: "bg-destructive text-destructive-fg font-sans whitespace-nowrap hover:bg-destructive-hover",
+ ghost: "text-foreground hover:bg-level-1 hover:text-accent whitespace-nowrap font-sans ",
+ link: "text-foreground hover:text-muted-1 font-sans ",
+ linkFG: "text-foreground hover:text-muted-1 font-sans ", // marker to style nav as regular link
+}
+
+const size = {
+ link: '',
+ sm: "h-9 px-3 rounded-md text-xs ",
+ default: "h-10 py-2 px-4 text-sm lg:min-w-[220px]",
+ lg: "h-10 px-8 text-base rounded-lg min-w-[260px] lg:min-w-[300px] lg:h-10 xs:min-w-0 xs:text-sm",
+ icon: "h-10 w-10",
+}
+
+const buttonVariants = cva(
+ "flex items-center justify-center rounded-md font-medium transition-colors " +
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 " +
+ "disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
+ {
+ variants: {
+ variant,
+ size
+ },
+ defaultVariants: {
+ variant: "primary",
+ size: "default",
+ },
+ }
+)
+
+type ButtonVariants = keyof typeof variant
+type ButtonSizes = keyof typeof size
+
+interface ButtonProps extends
+ React.ButtonHTMLAttributes,
+ VariantProps
+{
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+
+Button.displayName = "Button"
+
+export {
+ Button as default,
+ type ButtonProps,
+ type ButtonVariants,
+ type ButtonSizes,
+ buttonVariants,
+}
diff --git a/packages/ui/primitives/card.tsx b/packages/ui/primitives/card.tsx
new file mode 100644
index 00000000..4345326d
--- /dev/null
+++ b/packages/ui/primitives/card.tsx
@@ -0,0 +1,83 @@
+'use client'
+import * as React from "react"
+
+import { cn } from "../util"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ return(
+
+ )
+})
+
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/packages/ui/primitives/dialog-video-controller.tsx b/packages/ui/primitives/dialog-video-controller.tsx
new file mode 100644
index 00000000..cb1af95d
--- /dev/null
+++ b/packages/ui/primitives/dialog-video-controller.tsx
@@ -0,0 +1,38 @@
+'use client'
+
+import React, { PropsWithChildren, useState } from 'react'
+
+const DialogVideoController: React.FC = ({
+ children,
+}) => {
+
+ const [open, setOpen] = useState(false)
+
+ const onOpenChange = (b: boolean) => {
+ setOpen(b)
+ const videos = document.getElementsByTagName('video')
+ const videoArray = Array.from(videos)
+ videoArray.forEach((v) => {
+ if (b) {
+ v.pause()
+ }
+ else {
+ v.play()
+ }
+ })
+ }
+
+ // https://stackoverflow.com/a/49052730/11645689
+ const updatedChildren = React.Children.map(
+ children,
+ (child) => (React.cloneElement(
+ child as any, { open, onOpenChange }
+ ))
+ )
+
+ return (<>
+ {updatedChildren}
+ >)
+}
+
+export default DialogVideoController
diff --git a/packages/ui/primitives/dialog.tsx b/packages/ui/primitives/dialog.tsx
new file mode 100644
index 00000000..bbc0df55
--- /dev/null
+++ b/packages/ui/primitives/dialog.tsx
@@ -0,0 +1,145 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+import { classNames } from "../util/classNames"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = ({
+ children,
+ ...props
+}: DialogPrimitive.DialogPortalProps) => (
+
+
+ {children}
+
+
+)
+DialogPortal.displayName = DialogPrimitive.Portal.displayName
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+ Close
+
+));
+DialogClose.displayName = "DialogClose"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogContent,
+ DialogClose,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+ DialogOverlay,
+ DialogPortal,
+}
diff --git a/packages/ui/primitives/form.tsx b/packages/ui/primitives/form.tsx
new file mode 100644
index 00000000..4c37d4be
--- /dev/null
+++ b/packages/ui/primitives/form.tsx
@@ -0,0 +1,179 @@
+// @ts-nocheck
+'use client'
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { Slot } from "@radix-ui/react-slot"
+
+import {
+ Controller,
+ type ControllerProps,
+ FieldPath,
+ type FieldValues,
+ FormProvider,
+ useFormContext,
+} from "react-hook-form"
+
+import { cn } from "../util"
+import Label from "./label"
+
+const Form = FormProvider
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> = {
+ name: TName
+}
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue
+)
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ )
+}
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext)
+ const itemContext = React.useContext(FormItemContext)
+ const { getFieldState, formState } = useFormContext()
+
+ const fieldState = getFieldState(fieldContext.name, formState)
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ")
+ }
+
+ const { id } = itemContext
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
+
+type FormItemContextValue = {
+ id: string
+}
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue
+)
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const id = React.useId()
+
+ return (
+
+
+
+ )
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField()
+
+ return (
+
+ )
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+
+ )
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField()
+
+ return (
+
+ )
+})
+FormDescription.displayName = "FormDescription"
+
+const FormMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField()
+ const body = error ? String(error?.message) : children
+
+ const classes = 'text-xs font-medium py-1 ' +
+ (error ? 'text-destructive' : 'text-muted-1') +
+ (body ? '' : ' invisible') // Hold space in the layout
+
+ return (
+
+ {body ? body : 'dummy'}
+
+ )
+})
+FormMessage.displayName = "FormMessage"
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/packages/ui/primitives/index.ts b/packages/ui/primitives/index.ts
new file mode 100644
index 00000000..b6a34cc6
--- /dev/null
+++ b/packages/ui/primitives/index.ts
@@ -0,0 +1,76 @@
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './accordion'
+export { default as ApplyTypography, type TypographySize} from './apply-typography'
+
+export {
+ default as Button,
+ type ButtonProps,
+ type ButtonVariants,
+ type ButtonSizes,
+ buttonVariants,
+} from './button'
+
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './card'
+export { default as DialogVideoController } from './dialog-video-controller'
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+} from './dialog'
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+} from './form'
+
+export { default as Input } from './input'
+export { default as Label } from './label'
+export { default as MDXLink } from './mdx-link'
+
+export {
+ Sheet,
+ SheetPortal,
+ SheetOverlay,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+} from './sheet'
+
+export { default as TailwindIndicator } from './tailwind-indicator'
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+} from './toast'
+
+export { Toaster } from './toaster'
+export { useToast, toast } from './use-toast'
+
+export { default as VideoPlayer } from './video-player'
+
+
+
diff --git a/packages/ui/primitives/input.tsx b/packages/ui/primitives/input.tsx
new file mode 100644
index 00000000..1c9e347f
--- /dev/null
+++ b/packages/ui/primitives/input.tsx
@@ -0,0 +1,30 @@
+'use client'
+
+import React from "react"
+
+import { cn } from "../util"
+
+export interface InputProps
+ extends React.InputHTMLAttributes {}
+
+const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Input.displayName = "Input"
+
+export default Input
diff --git a/packages/ui/primitives/label.tsx b/packages/ui/primitives/label.tsx
new file mode 100644
index 00000000..8ffcedd7
--- /dev/null
+++ b/packages/ui/primitives/label.tsx
@@ -0,0 +1,28 @@
+// @ts-nocheck
+"use client"
+
+import React from "react"
+// @ts-ignore
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "../util"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export default Label
diff --git a/packages/ui/primitives/mdx-link.tsx b/packages/ui/primitives/mdx-link.tsx
new file mode 100644
index 00000000..d0954bed
--- /dev/null
+++ b/packages/ui/primitives/mdx-link.tsx
@@ -0,0 +1,22 @@
+import React, { type AnchorHTMLAttributes, type PropsWithChildren } from 'react'
+import Link from 'next/link'
+
+const MDXLink: React.FC & PropsWithChildren> = ({
+ href,
+ children,
+ ...rest
+}) => {
+
+ // internal link
+ if (href && (href.startsWith('/'))) {
+ return (
+
+ {children}
+
+ )
+ }
+
+ return {children}
+}
+
+export default MDXLink
diff --git a/packages/ui/primitives/sheet.tsx b/packages/ui/primitives/sheet.tsx
new file mode 100644
index 00000000..fbb83d8a
--- /dev/null
+++ b/packages/ui/primitives/sheet.tsx
@@ -0,0 +1,147 @@
+// @ts-nocheck
+
+'use client'
+
+import * as React from 'react'
+import * as SheetPrimitive from '@radix-ui/react-dialog'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { ChevronRight } from 'lucide-react'
+
+import { cn } from '../util'
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+ 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-300',
+ {
+ variants: {
+ side: {
+ top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
+ bottom:
+ 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
+ left: 'inset-y-0 left-0 h-full w-2/3 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
+ right:
+ 'inset-y-0 right-0 h-full w-2/3 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
+ },
+ },
+ defaultVariants: {
+ side: 'right',
+ },
+ }
+)
+
+interface SheetContentProps
+ extends React.ComponentPropsWithoutRef,
+ VariantProps {}
+
+const closeUIclx = 'rounded-sm opacity-70 ring-offset-background ' +
+ 'transition-opacity hover:opacity-100 disabled:pointer-events-none data-[state=open]:bg-secondary'
+
+const SheetContent = React.forwardRef<
+ React.ElementRef,
+ SheetContentProps & { closeButtonClass? : string}
+>(({ side = 'right', className, children, closeButtonClass, ...props }, ref) => (
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+SheetHeader.displayName = 'SheetHeader'
+
+const SheetFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+SheetFooter.displayName = 'SheetFooter'
+
+const SheetTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+ Sheet,
+ SheetPortal,
+ SheetOverlay,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/packages/ui/primitives/tailwind-indicator.tsx b/packages/ui/primitives/tailwind-indicator.tsx
new file mode 100644
index 00000000..43505c5a
--- /dev/null
+++ b/packages/ui/primitives/tailwind-indicator.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+
+const TailwindIndicator: React.FC = () => {
+
+ if (process.env.NODE_ENV === "production") return null
+
+ return (
+
+
xs
+
sm
+
md
+
lg
+
xl
+
2xl
+
+ )
+}
+
+export default TailwindIndicator
diff --git a/packages/ui/primitives/toast.tsx b/packages/ui/primitives/toast.tsx
new file mode 100644
index 00000000..0b53c81f
--- /dev/null
+++ b/packages/ui/primitives/toast.tsx
@@ -0,0 +1,129 @@
+// @ts-nocheck
+'use client'
+import * as React from "react"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+import { cn } from "../util"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-fg",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Toast = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, ...props }, ref) => {
+ return (
+
+ )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef
+
+type ToastActionElement = React.ReactElement
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+}
diff --git a/packages/ui/primitives/toaster.tsx b/packages/ui/primitives/toaster.tsx
new file mode 100644
index 00000000..3a159ddc
--- /dev/null
+++ b/packages/ui/primitives/toaster.tsx
@@ -0,0 +1,37 @@
+// @ts-nocheck
+"use client"
+
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "./toast"
+
+import { useToast } from "../primitives/use-toast"
+
+export const Toaster: React.FC = () => {
+ const { toasts } = useToast()
+
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+
+
+ {title && {title} }
+ {description && (
+ {description}
+ )}
+
+ {action}
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/packages/ui/primitives/use-toast.ts b/packages/ui/primitives/use-toast.ts
new file mode 100644
index 00000000..0d1d4dce
--- /dev/null
+++ b/packages/ui/primitives/use-toast.ts
@@ -0,0 +1,192 @@
+// Inspired by react-hot-toast library
+import * as React from "react"
+
+import type {
+ ToastActionElement,
+ ToastProps,
+} from "../primitives/toast"
+
+const TOAST_LIMIT = 1
+const TOAST_REMOVE_DELAY = 1000000
+
+type ToasterToast = ToastProps & {
+ id: string
+ title?: React.ReactNode
+ description?: React.ReactNode
+ action?: ToastActionElement
+}
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
+} as const
+
+let count = 0
+
+function genId() {
+ count = (count + 1) % Number.MAX_VALUE
+ return count.toString()
+}
+
+type ActionType = typeof actionTypes
+
+type Action =
+ | {
+ type: ActionType["ADD_TOAST"]
+ toast: ToasterToast
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"]
+ toast: Partial
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+
+interface State {
+ toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map>()
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId)
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ })
+ }, TOAST_REMOVE_DELAY)
+
+ toastTimeouts.set(toastId, timeout)
+}
+
+const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ }
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
+ ),
+ }
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId)
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id)
+ })
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t
+ ),
+ }
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ }
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ }
+ }
+}
+
+const listeners: Array<(state: State) => void> = []
+
+let memoryState: State = { toasts: [] }
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action)
+ listeners.forEach((listener) => {
+ listener(memoryState)
+ })
+}
+
+type Toast = Omit
+
+function toast({ ...props }: Toast) {
+ const id = genId()
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ })
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open: any) => {
+ if (!open) dismiss()
+ },
+ },
+ })
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ }
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState)
+
+ React.useEffect(() => {
+ listeners.push(setState)
+ return () => {
+ const index = listeners.indexOf(setState)
+ if (index > -1) {
+ listeners.splice(index, 1)
+ }
+ }
+ }, [state])
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ }
+}
+
+export { useToast, toast }
diff --git a/packages/ui/primitives/video-player.tsx b/packages/ui/primitives/video-player.tsx
new file mode 100644
index 00000000..f0b1c666
--- /dev/null
+++ b/packages/ui/primitives/video-player.tsx
@@ -0,0 +1,30 @@
+'use client'
+import React from 'react'
+
+import { Dimensions } from '../types'
+
+interface VideoProps extends React.ComponentPropsWithoutRef<"video"> {
+ sources: string[]
+ className?: string
+ constrainTo?: Dimensions
+}
+
+const VideoPlayer = React.forwardRef(
+({
+ sources,
+ className='',
+ constrainTo,
+ ...rest
+}, ref) => {
+
+
+ return (
+
+ {sources.map((source, index) => (
+
+ ))}
+
+ )
+})
+
+export default VideoPlayer
\ No newline at end of file
diff --git a/packages/ui/style/colors.tailwind.js b/packages/ui/style/colors.tailwind.js
new file mode 100644
index 00000000..b74a16bc
--- /dev/null
+++ b/packages/ui/style/colors.tailwind.js
@@ -0,0 +1,46 @@
+module.exports = ({ colors }) => ({
+ inherit: colors.inherit,
+ current: colors.current,
+ transparent: colors.transparent,
+ input: "var(--input)",
+ ring: "var(--ring)",
+ background: 'var(--bg-0)',
+ // levels forward in z-index (a la Material Design)
+ level: {
+ DEFAULT: "var(--bg-0)",
+ '0': "var(--bg-0)",
+ '1': "var(--bg-1)",
+ '2': "var(--bg-2)",
+ '3': "var(--bg-3)",
+ },
+ accent: "var(--fg-0)", // full contrast (darkTheme:white) : heading and links hover
+ foreground: "var(--fg-body)", // body off-white (bright enough to contrast to bg-level-1)
+ muted: {
+ DEFAULT: "var(--fg-2)", // de-emphasized: fine print
+ '1': "var(--fg-2)", // synonymous ^^^
+ '2': "var(--fg-3)", // disabled or very de-emphasized
+ '3': "var(--fg-4)", // very disabled ;)
+ '4': "var(--fg-5)", // disabled border
+ },
+ primary: {
+ DEFAULT: "var(--primary)",
+ lux: "var(--primary)",
+ hover: "var(--primary-hover)",
+ fg: "var(--primary-fg)",
+ },
+ secondary: {
+ DEFAULT: "var(--secondary)",
+ lux: "var(--secondary)",
+ hover: "var(--secondary-hover)",
+ fg: "var(--secondary-fg)",
+ },
+ destructive: {
+ DEFAULT: "var(--destructive)",
+ fg: "var(--destructive-fg)",
+ },
+ nav: {
+ DEFAULT: "var(--nav)",
+ hover: "var(--nav-hover)",
+ current: "var(--nav-current)",
+ },
+})
\ No newline at end of file
diff --git a/packages/ui/style/fonts.tailwind.js b/packages/ui/style/fonts.tailwind.js
new file mode 100644
index 00000000..fa22c0a7
--- /dev/null
+++ b/packages/ui/style/fonts.tailwind.js
@@ -0,0 +1,48 @@
+export const fontFamily = {
+ sans: [
+ 'var(--font-inter)',
+ // Do not provide an array of fallbacks.
+ // there's a bug in Next where they get listed out in this order so the
+ // last one cascades!
+ /*
+ 'ui-sans-serif',
+ 'system-ui',
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ '"Segoe UI"',
+ 'Roboto',
+ '"Helvetica Neue"',
+ 'Arial',
+ '"Noto Sans"',
+ 'sans-serif',
+ '"Apple Color Emoji"',
+ '"Segoe UI Emoji"',
+ '"Segoe UI Symbol"',
+ '"Noto Color Emoji"', */
+ ],
+ serif: ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
+ mono: [
+ 'ui-monospace',
+ 'SFMono-Regular',
+ 'Menlo',
+ 'Monaco',
+ 'Consolas',
+ '"Liberation Mono"',
+ '"Courier New"',
+ 'monospace',
+ ],
+ heading: ['var(--font-druk-text-wide)']
+}
+
+export const fontSize = {
+ xs: ['0.8rem', { lineHeight: '1rem' }], // fine print
+ sm: ['0.9rem', { lineHeight: '1.2rem' }], // 'standard' some news article cards (set manually when using typography-sm)
+ base: ['1rem', { lineHeight: 1.4 }],
+ lg: ['1.125rem', { lineHeight: '1.75rem' }],
+ xl: ['1.25rem', { lineHeight: '1.75rem' }],
+ '2xl': ['1.5rem', { lineHeight: '2rem' }],
+ '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
+ '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
+ '5xl': ['3rem', { lineHeight: '1' }],
+ '6xl': ['3.75rem', { lineHeight: '1' }],
+}
\ No newline at end of file
diff --git a/packages/ui/style/fonts/DrukTextWide-Bold-Trial.otf b/packages/ui/style/fonts/DrukTextWide-Bold-Trial.otf
new file mode 100644
index 00000000..9131384d
Binary files /dev/null and b/packages/ui/style/fonts/DrukTextWide-Bold-Trial.otf differ
diff --git a/packages/ui/style/fonts/DrukTextWide-Heavy-Trial.otf b/packages/ui/style/fonts/DrukTextWide-Heavy-Trial.otf
new file mode 100644
index 00000000..e22c6524
Binary files /dev/null and b/packages/ui/style/fonts/DrukTextWide-Heavy-Trial.otf differ
diff --git a/packages/ui/style/fonts/DrukTextWide-Medium-Trial.otf b/packages/ui/style/fonts/DrukTextWide-Medium-Trial.otf
new file mode 100644
index 00000000..f06fb723
Binary files /dev/null and b/packages/ui/style/fonts/DrukTextWide-Medium-Trial.otf differ
diff --git a/packages/ui/style/get-plugin-styles.js b/packages/ui/style/get-plugin-styles.js
new file mode 100644
index 00000000..2eabda87
--- /dev/null
+++ b/packages/ui/style/get-plugin-styles.js
@@ -0,0 +1,616 @@
+const {
+ round,
+ pxToRem,
+ pxToEm,
+} = require('../util')
+
+const typographyColorTheme = {
+ // vars are defined in global.css
+ // and dark mode is handled at that level.
+ '--tw-prose-body': "var(--fg-body)",
+ '--tw-prose-headings': "var(--fg-0)",
+ '--tw-prose-links': "var(--fg-0)",
+ '--tw-prose-links-hover': "var(--fg-2)",
+ '--tw-prose-bold': "var(--fg-0)",
+ '--tw-prose-counters': "var(--fg-4)",
+ '--tw-prose-bullets': "var(--fg-2)",
+ '--tw-prose-hr': "var(--fg-2)",
+ '--tw-prose-quotes': "var(--fg-body)",
+ '--tw-prose-quote-borders': "var(--fg-3)",
+ '--tw-prose-captions': "var(--fg-2)",
+ '--tw-prose-kbd': "var(--fg-0)",
+ '--tw-prose-kbd-shadows': "var(--fg-1)",
+ '--tw-prose-code': "var(--fg-0)",
+ '--tw-prose-pre-code': "var(--fg-1)",
+ '--tw-prose-pre-bg': "var(--bg-1)",
+ '--tw-prose-th-borders': "var(--fg-2)",
+ '--tw-prose-td-borders': "var(--fg-3)",
+}
+
+const defaultCSS = {
+
+ color: 'var(--tw-prose-body)',
+// YUCK maxWidth: '65ch',
+ p: {}, // Required to maintain correct order when merging
+ 'p:first-child': {},
+ 'p:last-child': {},
+ a: {
+ color: 'var(--tw-prose-links)',
+ textDecoration: 'underline',
+ fontWeight: '400',
+ },
+ 'a:hover': {
+ color: 'var(--tw-prose-links-hover)',
+ },
+ blockquote: {
+ fontWeight: '400',
+ fontStyle: 'italic',
+ color: 'var(--tw-prose-quotes)',
+ quotes: '"\\201C""\\201D""\\2018""\\2019"',
+ },
+ //'blockquote::before': {},
+ //'blockquote::after': {},
+ 'blockquote p:first-of-type::before': {
+ content: 'open-quote',
+ },
+ 'blockquote p:last-of-type::after': {
+ content: 'close-quote',
+ },
+ 'blockquote strong': {
+ color: 'inherit',
+ },
+ cite: {
+ display: 'block',
+ fontStyle: 'normal',
+ textAlign: 'right',
+ color: 'var(--tw-prose-quotes)',
+ },
+ ol: {
+ listStyleType: 'decimal',
+ },
+ ul: {
+ //listStyleType: 'disc',
+ },
+ 'ol > li::marker': {
+ fontWeight: '400',
+ color: 'var(--tw-prose-counters)',
+ },
+ 'ul > li::marker': {
+ color: 'var(--tw-prose-bullets)',
+ },
+ 'ul > li::before': {
+ },
+ hr: {},
+ hr: {
+ borderColor: 'var(--tw-prose-hr)',
+ borderTopWidth: 1,
+ },
+ h1: {
+ color: 'var(--tw-prose-headings)',
+ fontWeight: '800',
+ textAlign: 'center'
+ },
+ h2: {
+ color: 'var(--tw-prose-headings)',
+ fontWeight: '700',
+ textAlign: 'center'
+ },
+ h3: {
+ color: 'var(--tw-prose-headings)',
+ fontWeight: '600',
+ textAlign: 'center'
+ },
+ h4: {
+ color: 'var(--tw-prose-headings)',
+ fontWeight: '600',
+ },
+ h5: {
+ color: 'var(--tw-prose-headings)',
+ fontWeight: '600',
+ },
+ h6: {
+ color: 'var(--tw-prose-headings)',
+ },
+ img: {},
+ 'img:first-child': {
+ },
+ 'img:last-child': {
+ },
+ picture: {
+ display: 'block',
+ },
+ strong: {
+ color: 'var(--tw-prose-bold)',
+ fontWeight: '600',
+ },
+ video: {},
+ kbd: {
+ fontWeight: '500',
+ fontFamily: 'inherit',
+ color: 'var(--tw-prose-kbd)',
+ boxShadow:
+ '0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%)',
+ },
+ code: {
+ color: 'var(--tw-prose-code)',
+ fontWeight: '600',
+ },
+ 'code::before': {
+ content: '"`"',
+ },
+ 'code::after': {
+ content: '"`"',
+ },
+ 'a code': {
+ color: 'inherit',
+ },
+ 'h1 code': {
+ color: 'inherit',
+ },
+ 'h2 code': {
+ color: 'inherit',
+ },
+ 'h3 code': {
+ color: 'inherit',
+ },
+ 'h4 code': {
+ color: 'inherit',
+ },
+ 'h5 code': {
+ color: 'inherit',
+ },
+ 'h6 code': {
+ color: 'inherit',
+ },
+ 'blockquote code': {
+ color: 'inherit',
+ },
+ 'thead th code': {
+ color: 'inherit',
+ },
+ pre: {
+ color: 'var(--tw-prose-pre-code)',
+ backgroundColor: 'var(--tw-prose-pre-bg)',
+ overflowX: 'auto',
+ fontWeight: '400',
+ },
+ 'pre code': {
+ backgroundColor: 'transparent',
+ borderWidth: '0',
+ borderRadius: '0',
+ padding: '0',
+ fontWeight: 'inherit',
+ color: 'inherit',
+ fontSize: 'inherit',
+ fontFamily: 'inherit',
+ lineHeight: 'inherit',
+ },
+ 'pre code::before': {
+ content: 'none',
+ },
+ 'pre code::after': {
+ content: 'none',
+ },
+ table: {
+ width: '100%',
+ tableLayout: 'auto',
+ textAlign: 'left',
+ marginTop: pxToEm(32, 16),
+ marginBottom: pxToEm(32, 16),
+ },
+ thead: {
+ borderBottomWidth: '1px',
+ borderBottomColor: 'var(--tw-prose-th-borders)',
+ },
+ 'thead th': {
+ color: 'var(--tw-prose-headings)',
+ fontWeight: '600',
+ verticalAlign: 'bottom',
+ },
+ 'tbody tr': {
+ borderBottomWidth: '1px',
+ borderBottomColor: 'var(--tw-prose-td-borders)',
+ },
+ 'tbody tr:last-child': {
+ borderBottomWidth: '0',
+ },
+ 'tbody td': {
+ verticalAlign: 'baseline',
+ },
+ tfoot: {
+ borderTopWidth: '1px',
+ borderTopColor: 'var(--tw-prose-th-borders)',
+ },
+ 'tfoot td': {
+ verticalAlign: 'top',
+ },
+}
+
+const defaultModifiers = (base) => ({
+ base: {
+ css: [
+ {
+ fontSize: '1rem',
+ lineHeight: 1.4,
+ p: {
+ marginTop: '0.33rem',
+ marginBottom: '0.33rem',
+ },
+ 'p:first-child': {
+ marginTop: 0,
+ },
+ 'p:last-child': {
+ marginBottom: 0,
+ },
+ a: {},
+ 'a:hover': {},
+ blockquote: {
+ //color: 'blue', //var(--tw-prose-quotes)',
+ //marginTop: pxToEm(32, 20),
+ //marginBottom: pxToEm(32, 20),
+ //paddingLeft: pxToEm(20, 20),
+ },
+ //'blockquote::before': {},
+ //'blockquote::after': {},
+ 'blockquote p:first-of-type::before': {
+ },
+ 'blockquote p:last-of-type::after': {
+ },
+ cite: {},
+ ol: {
+ fontSize: '1rem',
+ paddingLeft: '1.5rem',
+ margin: 0,
+ marginLeft: '-5px',
+ },
+ ul: {
+ fontSize: '1rem',
+ paddingLeft: '1.5rem',
+ margin: 0,
+ marginLeft: '-0.6rem',
+ listStylePosition: 'outside',
+ listStyleType: "'\u25BC'"
+ },
+ li: {},
+ 'ol > li': {
+ paddingLeft: '0.4rem',
+ },
+ 'ul > li': {
+ paddingLeft: '0.75rem',
+ marginBottom: '0.75rem',
+ },
+ 'ul > li:last-child': {
+ marginBottom: 0,
+ },
+ '> ul > li p': {
+ margin: 0,
+ display: 'inline'
+ },
+ hr: {},
+ h1: {
+ fontSize: pxToRem(40, base),
+ lineHeight: 1.2,
+ },
+ h2: {
+ fontSize: pxToRem(36, base),
+ lineHeight: 1.2,
+ },
+ h3: {
+ fontSize: '1.4rem',
+ lineHeight: 1.2,
+ },
+ h4: {
+ marginTop: '0.5rem',
+ marginBottom: '0.5rem',
+ fontSize: '1.3rem',
+ lineHeight: 1.2,
+ },
+ h5: {
+ marginTop: '0.5rem',
+ marginBottom: '0.5rem',
+ fontSize: '1.2rem',
+ lineHeight: 1.4,
+ fontWeight: 400
+ },
+ h6: {
+ marginTop: '0.5rem',
+ marginBottom: '0.5rem',
+ fontSize: '1.15rem',
+ lineHeight: 1.3,
+ fontWeight: 400
+ },
+ 'hr + *': {
+ marginTop: '0',
+ },
+ 'h2 + *': {
+ marginTop: '0',
+ },
+ 'h3 + *': {
+ marginTop: '0',
+ },
+ 'h4 + *': {
+ marginTop: '0',
+ },
+ img: {
+ marginTop: '0.5rem',
+ marginBottom: '0.5rem',
+ },
+ 'img:first-child': {
+ marginTop: 0,
+ },
+ 'img:last-child': {
+ marginBottom: 0,
+ },
+ picture: {
+ marginTop: pxToRem(20, base),
+ marginBottom: pxToRem(20, base),
+ },
+ 'picture > img': {
+ marginTop: '0',
+ marginBottom: '0',
+ },
+ video: {
+ marginTop: pxToRem(20, base),
+ marginBottom: pxToRem(20, base),
+ },
+ kbd: {
+ fontSize: pxToEm(14, 16),
+ borderRadius: pxToRem(5, base),
+ paddingTop: pxToEm(3, 16),
+ paddingRight: pxToEm(6, 16),
+ paddingBottom: pxToEm(3, 16),
+ paddingLeft: pxToEm(6, 16),
+ },
+ code: {
+ fontSize: pxToEm(14, 16),
+ },
+ 'h2 code': {
+ fontSize: pxToEm(21, 24),
+ },
+ 'h3 code': {
+ fontSize: pxToEm(18, 20),
+ },
+ pre: {
+ fontSize: pxToRem(14, 16),
+ lineHeight: round(24 / 14),
+ marginTop: pxToEm(24, 14),
+ marginBottom: pxToEm(24, 14),
+ borderRadius: pxToRem(6, base),
+ paddingTop: pxToEm(12, 14),
+ paddingRight: pxToEm(16, 14),
+ paddingBottom: pxToEm(12, 14),
+ paddingLeft: pxToEm(16, 14),
+ },
+ /*
+ '> ul > li > *:first-child': {},
+ '> ul > li > *:last-child': {},
+ '> ol > li > *:first-child': {},
+ '> ol > li > *:last-child': {},
+ */
+ 'ul ul, ul ol, ol ul, ol ol': {
+ marginTop: pxToRem(12, base),
+ marginBottom: pxToRem(12, base),
+ },
+ dl: {
+ marginTop: pxToEm(20, 16),
+ marginBottom: pxToEm(20, base),
+ },
+ dt: {
+ marginTop: pxToEm(20, base),
+ },
+ dd: {
+ marginTop: pxToEm(8, base),
+ paddingLeft: pxToEm(26, base),
+ },
+ table: {
+ fontSize: pxToEm(14, base),
+ lineHeight: round(24 / 14),
+ },
+ 'thead th': {
+ paddingRight: pxToEm(8, 14),
+ paddingBottom: pxToEm(8, 14),
+ paddingLeft: pxToEm(8, 14),
+ },
+ 'thead th:first-child': {
+ paddingLeft: '0',
+ },
+ 'thead th:last-child': {
+ paddingRight: '0',
+ },
+ 'tbody td, tfoot td': {
+ paddingTop: pxToEm(8, 14),
+ paddingRight: pxToEm(8, 14),
+ paddingBottom: pxToEm(8, 14),
+ paddingLeft: pxToEm(8, 14),
+ },
+ 'tbody td:first-child, tfoot td:first-child': {
+ paddingLeft: '0',
+ },
+ 'tbody td:last-child, tfoot td:last-child': {
+ paddingRight: '0',
+ },
+ },
+ {
+ '> :first-child': {
+ marginTop: '0',
+ },
+ '> :last-child': {
+ marginBottom: '0',
+ },
+ },
+ ],
+ },
+ sm: {
+ css: [
+ {
+ p: {},
+ 'p:first-child': {},
+ 'p:last-child': {},
+ a: {},
+ 'a:hover': {},
+ blockquote: {},
+ //'blockquote::before': {},
+ //'blockquote::after': {},
+ 'blockquote p:first-of-type::before': {},
+ 'blockquote p:last-of-type::after': {},
+ cite: {},
+ ol: {},
+ ul: {},
+ li: {},
+ 'ol > li': {},
+ 'ul > li': {},
+ 'ul > li:last-child': {},
+ '> ul > li p': {},
+ hr: {},
+ h1: {
+ fontSize: pxToRem(24, base),
+ },
+ h2: {
+ fontSize: pxToRem(20, base),
+ },
+ h3: {
+ fontSize: pxToEm(18, base),
+ },
+ h4: {},
+ h5: {},
+ h6: {},
+ 'hr + *': {},
+ 'h2 + *': {},
+ 'h3 + *': {},
+ 'h4 + *': {},
+ img: {},
+ 'img:first-child': {},
+ 'img:last-child': {},
+ picture: {},
+ 'picture > img': {},
+ video: {},
+ kbd: {},
+ code: {},
+ 'h2 code': {},
+ 'h3 code': {},
+ pre: {},
+ /*
+ '> ul > li > *:first-child': {},
+ '> ul > li > *:last-child': {},
+ '> ol > li > *:first-child': {},
+ '> ol > li > *:last-child': {},
+ */
+ 'ul ul, ul ol, ol ul, ol ol': {},
+ dl: {},
+ dt:{},
+ dd:{},
+ table: {},
+ 'thead th': {},
+ 'thead th:first-child': {},
+ 'thead th:last-child': {},
+ 'tbody td, tfoot td': {},
+ 'tbody td:first-child, tfoot td:first-child': {},
+ 'tbody td:last-child, tfoot td:last-child': {},
+ },
+ {
+ '> :first-child': {},
+ '> :last-child': {},
+ },
+ ],
+ },
+ lg: {
+ css: [
+ {
+ p: {},
+ 'p:first-child': {},
+ 'p:last-child': {},
+ a: {},
+ 'a:hover': {},
+ blockquote: {},
+ //'blockquote::before': {},
+ //'blockquote::after': {},
+ 'blockquote p:first-of-type::before': {},
+ 'blockquote p:last-of-type::after': {},
+ cite: {},
+ ol: {},
+ ul: {},
+ li: {},
+ 'ol > li': {},
+ 'ul > li': {},
+ 'ul > li:last-child': {},
+ '> ul > li p': {},
+ hr: {},
+ h1: {
+ fontSize: pxToRem(55, base),
+ lineHeight: 1.1,
+ },
+ h2: {
+ fontSize: pxToRem(45, base),
+ lineHeight: 1.1,
+ },
+ h3: {
+ fontSize: pxToRem(32, base),
+ lineHeight: 1.1,
+ },
+ h4: {
+ fontSize: pxToRem(26, base),
+ marginTop: pxToRem(13, base),
+ marginBottom: pxToRem(13, base),
+ fontWeight: 500
+ },
+ h5: {
+ fontSize: pxToRem(22, base),
+ marginTop: pxToRem(11, base),
+ marginBottom: pxToRem(11, base),
+ fontWeight: 400
+ },
+ h6: {},
+ 'hr + *': {},
+ 'h2 + *': {},
+ 'h3 + *': {},
+ 'h4 + *': {},
+ img: {},
+ 'img:first-child': {},
+ 'img:last-child': {},
+ picture: {},
+ 'picture > img': {},
+ video: {},
+ kbd: {},
+ code: {},
+ 'h2 code': {},
+ 'h3 code': {},
+ pre: {},
+ /*
+ '> ul > li > *:first-child': {},
+ '> ul > li > *:last-child': {},
+ '> ol > li > *:first-child': {},
+ '> ol > li > *:last-child': {},
+ */
+ 'ul ul, ul ol, ol ul, ol ol': {},
+ dl: {},
+ dt:{},
+ dd:{},
+ table: {},
+ 'thead th': {},
+ 'thead th:first-child': {},
+ 'thead th:last-child': {},
+ 'tbody td, tfoot td': {},
+ 'tbody td:first-child, tfoot td:first-child': {},
+ 'tbody td:last-child, tfoot td:last-child': {},
+ },
+ {
+ '> :first-child': {},
+ '> :last-child': {},
+ },
+ ],
+ },
+})
+
+const getStyles = (baseFontSize) => ({
+ DEFAULT: {
+ css: [
+ defaultCSS,
+ typographyColorTheme,
+ ...defaultModifiers(baseFontSize).base.css,
+ ],
+ },
+ sm: defaultModifiers(baseFontSize).sm,
+ lg: defaultModifiers(baseFontSize).lg,
+})
+
+module.exports = getStyles
diff --git a/packages/ui/style/globals.css b/packages/ui/style/globals.css
new file mode 100644
index 00000000..5d468a44
--- /dev/null
+++ b/packages/ui/style/globals.css
@@ -0,0 +1,113 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Preflight is applied first:
+https://unpkg.com/tailwindcss@3.3.5/src/css/preflight.css
+*/
+
+@layer base {
+
+ :root, .light, .light-theme {
+
+ --fg-0: hsl(0 0% 0%);
+ --fg-body: hsl(0 0% 10%);
+ --fg-1: hsl(0 0% 20%);
+ --fg-2: hsl(0 0% 35%);
+ --fg-3: hsl(0 0% 50%);
+ --fg-4: hsl(0 0% 70%);
+ --fg-5: hsl(0 0% 90%);
+
+ --bg-0: hsl(0 0% 100%);
+ --bg-1: hsl(0 0% 90%);
+ --bg-2: hsl(0 0% 75%);
+ --bg-3: hsl(0 0% 55%);
+
+ --bg-inverted: var(--fg-0);
+ --bg-inverted-hover: hsla(0, 0%, 0%, 0.85);
+
+ --fg-inverted: var(--bg-0);
+
+ --secondary-0: hsl(266, 79%, 80%);
+ --secondary-1: hsl(266, 79%, 70%);
+ --secondary-2: hsl(266, 79%, 60%);
+ --secondary-3: hsl(266, 79%, 50%);
+
+ --primary: var(--bg-inverted);
+ --primary-hover: var(--bg-inverted-hover);
+ --primary-fg: var(--fg-inverted);
+
+ --secondary: var(--secondary-0);
+ --secondary-hover: var(--secondary-2);
+ --secondary-fg: var(--fg-0);
+
+ --nav: var(--fg-4);
+ --nav-current: var(--fg-0);
+ --nav-hover: var(--fg-0);
+
+ --destructive: hsl(0 62.8% 45%);
+ --destructive-hover: hsl(0 62.8% 25%);
+ --destructive-fg: hsl(0 0% 100%);
+
+ --input: var(--bg-1);
+ --ring: var(--fg-1);
+
+ --radius: 0.5rem;
+ }
+
+ .dark, .dark-theme {
+
+ --fg-0: hsl(0 0% 100%);
+ --fg-body: hsl(0, 0%, 97%);
+ --fg-1: hsl(0 0% 85%);
+ --fg-2: hsl(0 0% 70%);
+ --fg-3: hsl(0 0% 55%);
+ --fg-4: hsl(0 0% 40%);
+ --fg-5: hsl(0 0% 25%);
+
+ --bg-inverted: var(--fg-0);
+ --bg-inverted-hover: hsla(0, 0%, 100%, 0.85);
+
+ --bg-3: hsl(0 0% 25%);
+ --bg-2: hsl(0 0% 18%);
+ --bg-1: hsl(0 0% 12%);
+ --bg-0: hsl(0 0% 0%);
+
+ --fg-inverted: var(--bg-0);
+
+ --secondary-0: hsl(266, 79%, 40%);
+ --secondary-1: hsl(266, 79%, 50%);
+ --secondary-2: hsl(266, 79%, 60%);
+ --secondary-3: hsl(266, 79%, 70%);
+ }
+}
+
+@layer base {
+ * {
+ @apply border-muted-3;
+ box-sizing: inherit;
+ }
+
+ /*
+ using this approach:
+ https://stackoverflow.com/a/76066443/11645689
+ see src/app/(home)/scroll-snap
+ */
+
+ html {
+ scroll-snap-type: y mandatory;
+ height: 100vh;
+
+ overflow-y: scroll;
+ box-sizing: border-box;
+
+ scrollbar-width: none;
+ }
+
+ body {
+ font-feature-settings: "rlig" 1, "calt" 1;
+ /* Match text on cards from old site */
+ font-size: 16px;
+ letter-spacing: 0.15px;
+ }
+}
diff --git a/packages/ui/style/nextFonts.ts b/packages/ui/style/nextFonts.ts
new file mode 100644
index 00000000..9d059992
--- /dev/null
+++ b/packages/ui/style/nextFonts.ts
@@ -0,0 +1,34 @@
+import { Inter } from 'next/font/google'
+import localFont from 'next/font/local'
+
+const drukTextWide = localFont({
+ src: [
+ {
+ path: './fonts/DrukTextWide-Medium-Trial.otf',
+ weight: '500',
+ style: 'normal'
+ },
+ {
+ path: './fonts/DrukTextWide-Bold-Trial.otf',
+ weight: '700',
+ style: 'normal'
+ },
+ {
+ path: './fonts/DrukTextWide-Heavy-Trial.otf',
+ weight: '800',
+ style: 'normal',
+ },
+ ],
+ display: 'swap',
+ variable: '--font-druk-text-wide' ,
+})
+
+const inter = Inter({
+ subsets: ["latin"],
+ variable: "--font-inter",
+})
+
+export {
+ inter,
+ drukTextWide
+}
diff --git a/packages/ui/style/safelist.tailwind.js b/packages/ui/style/safelist.tailwind.js
new file mode 100644
index 00000000..f2c46eed
--- /dev/null
+++ b/packages/ui/style/safelist.tailwind.js
@@ -0,0 +1,27 @@
+ // https://tailwindcss.com/docs/content-configuration#dynamic-class-names
+ // https://tailwindcss.com/docs/content-configuration#safelisting-classes
+
+ // these cannot be dynamically generated either. grr
+module.exports = [
+ 'sm:grid-cols-2',
+ 'sm:grid-cols-3',
+ 'sm:grid-cols-4',
+ 'sm:grid-cols-5',
+ 'sm:grid-cols-6',
+ 'md:grid-cols-2',
+ 'md:grid-cols-3',
+ 'md:grid-cols-4',
+ 'md:grid-cols-5',
+ 'md:grid-cols-6',
+ 'lg:grid-cols-2',
+ 'lg:grid-cols-3',
+ 'lg:grid-cols-4',
+ 'lg:grid-cols-5',
+ 'lg:grid-cols-6',
+ 'lg:columns-2',
+ 'lg:columns-3',
+ 'lg:columns-4',
+ 'lg:columns-5',
+ 'lg:columns-6',
+ 'lg:columns-7',
+]
\ No newline at end of file
diff --git a/packages/ui/style/screens.tailwind.js b/packages/ui/style/screens.tailwind.js
new file mode 100644
index 00000000..6fcfd148
--- /dev/null
+++ b/packages/ui/style/screens.tailwind.js
@@ -0,0 +1,8 @@
+module.exports = {
+ xs: '0px',
+ sm: '450px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1500px', // to match other sites
+}
diff --git a/packages/ui/style/social-svg.css b/packages/ui/style/social-svg.css
new file mode 100644
index 00000000..c2ab7ef0
--- /dev/null
+++ b/packages/ui/style/social-svg.css
@@ -0,0 +1,3 @@
+.social-icon svg {
+ transform: scale(1.25);
+}
diff --git a/packages/ui/style/typo-plugin/demo/MarkdownSample.mdx b/packages/ui/style/typo-plugin/demo/MarkdownSample.mdx
new file mode 100644
index 00000000..1350ea7e
--- /dev/null
+++ b/packages/ui/style/typo-plugin/demo/MarkdownSample.mdx
@@ -0,0 +1,223 @@
+
+ Until now, trying to style an article, document, or blog post with Tailwind has been a tedious
+ task that required a keen eye for typography and a lot of complex custom CSS.
+
+
+By default, Tailwind removes all of the default browser styling from paragraphs, headings, lists and more. This ends up being really useful for building application UIs because you spend less time undoing user-agent styles, but when you _really are_ just trying to style some content that came from a rich-text editor in a CMS or a markdown file, it can be surprising and unintuitive.
+
+We get lots of complaints about it actually, with people regularly asking us things like:
+
+> Why is Tailwind removing the default styles on my `h1` elements? How do I disable this? What do you mean I lose all the other base styles too?
+
+We hear you, but we're not convinced that simply disabling our base styles is what you really want. You don't want to have to remove annoying margins every time you use a `p` element in a piece of your dashboard UI. And I doubt you really want your blog posts to use the user-agent styles either — you want them to look _awesome_, not awful.
+
+The `@tailwindcss/typography` plugin is our attempt to give you what you _actually_ want, without any of the downsides of doing something stupid like disabling our base styles.
+
+It adds a new `prose` class that you can slap on any block of vanilla HTML content and turn it into a beautiful, well-formatted document:
+
+```html
+
+ Garlic bread with cheese: What the science tells us
+
+ For years parents have espoused the health benefits of eating garlic bread with cheese to their
+ children, with the food earning such an iconic status in our culture that kids will often dress
+ up as warm, cheesy loaf for Halloween.
+
+
+ But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases
+ springing up around the country.
+
+
+
+```
+
+For more information about how to use the plugin and the features it includes, [read the documentation](https://github.com/tailwindcss/typography/blob/master/README.md).
+
+---
+
+## What to expect from here on out
+
+What follows from here is just a bunch of absolute nonsense I've written to dogfood the plugin itself. It includes every sensible typographic element I could think of, like **bold text**, unordered lists, ordered lists, code blocks, block quotes, _and even italics_.
+
+It's important to cover all of these use cases for a few reasons:
+
+1. We want everything to look good out of the box.
+2. Really just the first reason, that's the whole point of the plugin.
+3. Here's a third pretend reason though a list with three items looks more realistic than a list with two items.
+
+Now we're going to try out another header style.
+
+### Typography should be easy
+
+So that's a header for you — with any luck if we've done our job correctly that will look pretty reasonable.
+
+Something a wise person once told me about typography is:
+
+> Typography is pretty important if you don't want your stuff to look like trash. Make it good then it won't be bad.
+
+It's probably important that images look okay here by default as well:
+
+
+
+
+ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of
+ classical Latin literature from 45 BC, making it over 2000 years old.
+
+
+
+Now I'm going to show you an example of an unordered list to make sure that looks good, too:
+
+- So here is the first item in this list.
+- In this example we're keeping the items short.
+- Later, we'll use longer, more complex list items.
+
+And that's the end of this section.
+
+## What if we stack headings?
+
+### We should make sure that looks good, too.
+
+Sometimes you have headings directly underneath each other. In those cases you often have to undo the top margin on the second heading because it usually looks better for the headings to be closer together than a paragraph followed by a heading should be.
+
+### When a heading comes after a paragraph …
+
+When a heading comes after a paragraph, we need a bit more space, like I already mentioned above. Now let's see what a more complex list would look like.
+
+- **I often do this thing where list items have headings.**
+
+ For some reason I think this looks cool which is unfortunate because it's pretty annoying to get the styles right.
+
+ I often have two or three paragraphs in these list items, too, so the hard part is getting the spacing between the paragraphs, list item heading, and separate list items to all make sense. Pretty tough honestly, you could make a strong argument that you just shouldn't write this way.
+
+- **Since this is a list, I need at least two items.**
+
+ I explained what I'm doing already in the previous list item, but a list wouldn't be a list if it only had one item, and we really want this to look realistic. That's why I've added this second list item so I actually have something to look at when writing the styles.
+
+- **It's not a bad idea to add a third item either.**
+
+ I think it probably would've been fine to just use two items but three is definitely not worse, and since I seem to be having no trouble making up arbitrary things to type, I might as well include it. I'm going to press Enter now.
+
+After this sort of list I usually have a closing statement or paragraph, because it kinda looks weird jumping right to a heading.
+
+## Code should look okay by default.
+
+I think most people are going to use [highlight.js](https://highlightjs.org/) or [Prism](https://prismjs.com/) or something if they want to style their code blocks but it wouldn't hurt to make them look _okay_ out of the box, even with no syntax highlighting.
+
+Here's what a default `tailwind.config.js` file looks like at the time of writing:
+
+```js
+module.exports = {
+ purge: [],
+ theme: {
+ extend: {},
+ },
+ variants: {},
+ plugins: [],
+}
+```
+
+Hopefully that looks good enough to you.
+
+### What about nested lists?
+
+Nested lists basically always look bad which is why editors like Medium don't even let you do it, but I guess since some of you goofballs are going to do it we have to carry the burden of at least making it work.
+
+1. **Nested lists are rarely a good idea.**
+ - You might feel like you are being really "organized" or something but you are just creating a gross shape on the screen that is hard to read.
+ - Nested navigation in UIs is a bad idea too, keep things as flat as possible.
+ - Nesting tons of folders in your source code is also not helpful.
+2. **Since we need to have more items, here's another one.**
+ - I'm not sure if we'll bother styling more than two levels deep.
+ - Two is already too much, three is guaranteed to be a bad idea.
+ - If you nest four levels deep you belong in prison.
+3. **Two items isn't really a list, three is good though.**
+ - Again please don't nest lists if you want people to actually read your content.
+ - Nobody wants to look at this.
+ - I'm upset that we even have to bother styling this.
+
+The most annoying thing about lists in Markdown is that `` elements aren't given a child `` tag unless there are multiple paragraphs in the list item. That means I have to worry about styling that annoying situation too.
+
+- **For example, here's another nested list.**
+
+ But this time with a second paragraph.
+
+ - These list items won't have `
` tags
+ - Because they are only one line each
+
+- **But in this second top-level list item, they will.**
+
+ This is especially annoying because of the spacing on this paragraph.
+
+ - As you can see here, because I've added a second line, this list item now has a `
` tag.
+
+ This is the second line I'm talking about by the way.
+
+ - Finally here's another list item so it's more like a list.
+
+- A closing list item, but with no nested list, because why not?
+
+And finally a sentence to close off this section.
+
+## We didn't forget about description lists
+
+Well, that's not exactly true, we first released this plugin back in 2020 and it took three years before we added description lists. But they're here now, so let's just be happy about that…okay? They can be great for things like FAQs.
+
+
+ Why do you never see elephants hiding in trees?
+
+ Because they're so good at it. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quas
+ cupiditate laboriosam fugiat.
+
+ What do you call someone with no body and no nose?
+
+ Nobody knows. Lorem ipsum dolor sit amet consectetur adipisicing elit. Culpa, voluptas ipsa quia
+ excepturi, quibusdam natus exercitationem sapiente tempore labore voluptatem.
+
+ Why can't you hear a pterodactyl go to the bathroom?
+
+ Because the pee is silent. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsam, quas
+ voluptatibus ex culpa ipsum, aspernatur blanditiis fugiat ullam magnam suscipit deserunt illum
+ natus facilis atque vero consequatur! Quisquam, debitis error.
+
+
+
+## There are other elements we need to style
+
+I almost forgot to mention links, like [this link to the Tailwind CSS website](https://tailwindcss.com). We almost made them blue but that's so yesterday, so we went with dark gray, feels edgier.
+
+We even included table styles, check it out:
+
+| Wrestler | Origin | Finisher |
+| ----------------------- | ------------ | ------------------ |
+| Bret "The Hitman" Hart | Calgary, AB | Sharpshooter |
+| Stone Cold Steve Austin | Austin, TX | Stone Cold Stunner |
+| Randy Savage | Sarasota, FL | Elbow Drop |
+| Vader | Boulder, CO | Vader Bomb |
+| Razor Ramon | Chuluota, FL | Razor's Edge |
+
+We also need to make sure inline code looks good, like if I wanted to talk about `` elements or tell you the good news about `@tailwindcss/typography`.
+
+### Sometimes I even use `code` in headings
+
+Even though it's probably a bad idea, and historically I've had a hard time making it look good. This _"wrap the code blocks in backticks"_ trick works pretty well though really.
+
+Another thing I've done in the past is put a `code` tag inside of a link, like if I wanted to tell you about the [`tailwindcss/docs`](https://github.com/tailwindcss/docs) repository. I don't love that there is an underline below the backticks but it is absolutely not worth the madness it would require to avoid it.
+
+#### We haven't used an `h4` yet
+
+But now we have. Please don't use `h5` or `h6` in your content, Medium only supports two heading levels for a reason, you animals. I honestly considered using a `before` pseudo-element to scream at you if you use an `h5` or `h6`.
+
+We don't style them at all out of the box because `h4` elements are already so small that they are the same size as the body copy. What are we supposed to do with an `h5`, make it _smaller_ than the body copy? No thanks.
+
+### We still need to think about stacked headings though.
+
+#### Let's make sure we don't screw that up with `h4` elements, either.
+
+Phew, with any luck we have styled the headings above this text and they look pretty good.
+
+Let's add a closing paragraph here so things end with a decently sized block of text. I can't explain why I want things to end that way but I have to assume it's because I think things will look weird or unbalanced if there is a heading too close to the end of the document.
+
+What I've written here is probably long enough, but adding this final sentence can't hurt.
diff --git a/packages/ui/style/typo-plugin/demo/MarkdownSampleShort.mdx b/packages/ui/style/typo-plugin/demo/MarkdownSampleShort.mdx
new file mode 100644
index 00000000..b92b55fe
--- /dev/null
+++ b/packages/ui/style/typo-plugin/demo/MarkdownSampleShort.mdx
@@ -0,0 +1,60 @@
+
+ Until now, trying to style an article, document, or blog post with Tailwind has been a very
+ tedious task.
+
+
+The `@tailwindcss/typography` plugin is our attempt to give you what you _actually_ want, without any of the downsides of doing something stupid like disabling our base styles.
+
+> Why is Tailwind removing the default styles on my `h1` elements? How do I disable this? What do you mean I lose all the other base styles too?
+
+
+
Shouldn't be colored
+
+
+```html
+
+ But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases
+ springing up around the country.
+
+```
+
+For more information about how to use the plugin and the features it includes, [read the documentation](https://github.com/tailwindcss/typography/blob/master/README.md).
+
+---
+
+## This is a heading
+
+1. We want everything to look good out of the box.
+2. Really just the first reason, that's the whole point of the plugin.
+3. Here's a third pretend reason though a list with three items looks more realistic than a list with two items.
+
+Now **I'm going to show you** an example of an unordered list to make sure that looks good, too:
+
+- So here is the first item in this list.
+- In this example we're keeping the items short.
+- Later, we'll use longer, more complex list items.
+
+Let's even style a table:
+
+| Wrestler | Origin | Finisher |
+| ----------------------- | ------------ | ------------------ |
+| Bret "The Hitman" Hart | Calgary, AB | Sharpshooter |
+| Stone Cold Steve Austin | Austin, TX | Stone Cold Stunner |
+| Randy Savage | Sarasota, FL | Elbow Drop |
+| Vader | Boulder, CO | Vader Bomb |
+| Razor Ramon | Chuluota, FL | Razor's Edge |
+
+Finally, a figure with a caption:
+
+
+
+
+ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of
+ classical Latin literature from 45 BC, making it over 2000 years old.
+
+
+
+And that's the end of our little demo.
diff --git a/packages/ui/style/typo-plugin/demo/dark.js b/packages/ui/style/typo-plugin/demo/dark.js
new file mode 100644
index 00000000..2fde453f
--- /dev/null
+++ b/packages/ui/style/typo-plugin/demo/dark.js
@@ -0,0 +1,24 @@
+import Head from 'next/head'
+import MarkdownSampleShort from '../components/MarkdownSampleShort.mdx'
+
+export default function Index() {
+ return (
+
+
+
Tailwind CSS Typography
+
+
+
+
+ Are you happy now?
+
+
+
+
+
+ )
+}
+
+export const config = {
+ unstable_runtimeJS: false,
+}
diff --git a/packages/ui/style/typo-plugin/demo/list-items.js b/packages/ui/style/typo-plugin/demo/list-items.js
new file mode 100644
index 00000000..dc2f003c
--- /dev/null
+++ b/packages/ui/style/typo-plugin/demo/list-items.js
@@ -0,0 +1,176 @@
+import Head from 'next/head'
+import MarkdownSampleShort from '../components/MarkdownSampleShort.mdx'
+
+export default function Index() {
+ return (
+
+
+
Tailwind CSS Typography
+
+
+
+
+
prose-sm
+
It's important to cover all of these use cases for a few reasons:
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+ Here's a third pretend reason though a list with three items looks more realistic than
+ a list with two items.
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+
Now we're going to try out another header style.
+
+ So here is the first item in this list.
+ In this example we're keeping the items short.
+ Later, we'll use longer, more complex list items.
+
+
+
+
+
+
+
prose-base
+
It's important to cover all of these use cases for a few reasons:
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+ Here's a third pretend reason though a list with three items looks more realistic than
+ a list with two items.
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+
Now we're going to try out another header style.
+
+ So here is the first item in this list.
+ In this example we're keeping the items short.
+ Later, we'll use longer, more complex list items.
+
+
+
+
+
+
+
prose-lg
+
It's important to cover all of these use cases for a few reasons:
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+ Here's a third pretend reason though a list with three items looks more realistic than
+ a list with two items.
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+
Now we're going to try out another header style.
+
+ So here is the first item in this list.
+ In this example we're keeping the items short.
+ Later, we'll use longer, more complex list items.
+
+
+
+
+
+
+
prose-xl
+
It's important to cover all of these use cases for a few reasons:
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+ Here's a third pretend reason though a list with three items looks more realistic than
+ a list with two items.
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+
Now we're going to try out another header style.
+
+ So here is the first item in this list.
+ In this example we're keeping the items short.
+ Later, we'll use longer, more complex list items.
+
+
+
+
+
+
+
prose-2xl
+
It's important to cover all of these use cases for a few reasons:
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+ Here's a third pretend reason though a list with three items looks more realistic than
+ a list with two items.
+
+ We want everything to look good out of the box.
+ Really just the first reason, that's the whole point of the plugin.
+
+
Now we're going to try out another header style.
+
+ So here is the first item in this list.
+ In this example we're keeping the items short.
+ Later, we'll use longer, more complex list items.
+
+
+
+
+ )
+}
+
+export const config = {
+ unstable_runtimeJS: false,
+}
diff --git a/packages/ui/style/typo-plugin/demo/themes.js b/packages/ui/style/typo-plugin/demo/themes.js
new file mode 100644
index 00000000..0cdfd831
--- /dev/null
+++ b/packages/ui/style/typo-plugin/demo/themes.js
@@ -0,0 +1,282 @@
+import Head from 'next/head'
+import MarkdownSampleShort from '../components/MarkdownSampleShort.mdx'
+
+export default function Index() {
+ return (
+
+
+
Tailwind CSS Typography
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export const config = {
+ unstable_runtimeJS: false,
+}
diff --git a/packages/ui/style/typo-plugin/demo/variants.js b/packages/ui/style/typo-plugin/demo/variants.js
new file mode 100644
index 00000000..426106a8
--- /dev/null
+++ b/packages/ui/style/typo-plugin/demo/variants.js
@@ -0,0 +1,64 @@
+import Head from 'next/head'
+import MarkdownSampleShort from '../components/MarkdownSampleShort.mdx'
+
+export default function Variants() {
+ return (
+
+
+
Tailwind CSS Typography
+
+
+
+ Tailwind CSS Typography
+
+
+
+
+ )
+}
diff --git a/packages/ui/style/typo-plugin/index.d.ts b/packages/ui/style/typo-plugin/index.d.ts
new file mode 100644
index 00000000..5c38097c
--- /dev/null
+++ b/packages/ui/style/typo-plugin/index.d.ts
@@ -0,0 +1,9 @@
+declare function plugin(options?: Partial<{ className: string; target: 'modern' | 'legacy' }>): {
+ handler: () => void
+}
+
+declare namespace plugin {
+ const __isOptionsFunction: true
+}
+
+export = plugin
diff --git a/packages/ui/style/typo-plugin/index.js b/packages/ui/style/typo-plugin/index.js
new file mode 100644
index 00000000..d3b49d43
--- /dev/null
+++ b/packages/ui/style/typo-plugin/index.js
@@ -0,0 +1,141 @@
+const plugin = require('tailwindcss/plugin')
+const merge = require('lodash.merge')
+const castArray = require('lodash.castarray')
+const getStyles = require('../get-plugin-styles')
+const { commonTrailingPseudos } = require('./utils')
+
+let baseFontSize = 16
+
+const computed = {
+ // Reserved for future "magic properties", for example:
+ // bulletColor: (color) => ({ 'ul > li::before': { backgroundColor: color } }),
+}
+
+function inWhere(selector, { className, modifier, prefix }) {
+ let prefixedNot = prefix(`.not-${className}`).slice(1)
+ let selectorPrefix = selector.startsWith('>')
+ ? `${modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`} `
+ : ''
+
+ // Parse the selector, if every component ends in the same pseudo element(s) then move it to the end
+ let [trailingPseudo, rebuiltSelector] = commonTrailingPseudos(selector)
+
+ if (trailingPseudo) {
+ return `:where(${selectorPrefix}${rebuiltSelector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))${trailingPseudo}`
+ }
+
+ return `:where(${selectorPrefix}${selector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))`
+}
+
+function isObject(value) {
+ return typeof value === 'object' && value !== null
+}
+
+function configToCss(config = {}, { target, className, modifier, prefix }) {
+ function updateSelector(k, v) {
+ if (target === 'legacy') {
+ return [k, v]
+ }
+
+ if (Array.isArray(v)) {
+ return [k, v]
+ }
+
+ if (isObject(v)) {
+ let nested = Object.values(v).some(isObject)
+ if (nested) {
+ return [
+ inWhere(k, { className, modifier, prefix }),
+ v,
+ Object.fromEntries(Object.entries(v).map(([k, v]) => updateSelector(k, v))),
+ ]
+ }
+
+ return [inWhere(k, { className, modifier, prefix }), v]
+ }
+
+ return [k, v]
+ }
+
+ return Object.fromEntries(
+ Object.entries(
+ merge(
+ {},
+ ...Object.keys(config)
+ .filter((key) => computed[key])
+ .map((key) => computed[key](config[key])),
+ ...castArray(config.css || {})
+ )
+ ).map(([k, v]) => updateSelector(k, v))
+ )
+}
+
+module.exports = plugin.withOptions(
+ ({ className = 'prose', target = 'modern', base = 16 } = {}) => {
+ baseFontSize = base
+ return function ({ addVariant, addComponents, theme, prefix }) {
+ let modifiers = theme('typography')
+
+ let options = { className, prefix }
+
+ for (let [name, ...selectors] of [
+ ['headings', 'h1', 'h2', 'h3', /* 'h4', 'h5', 'h6', 'th' */],
+ ['h1'],
+ ['h2'],
+ ['h3'],
+ ['h4'],
+ ['h5'],
+ ['h6'],
+ ['p'],
+ ['a'],
+ ['blockquote'],
+ ['figure'],
+ ['figcaption'],
+ ['strong'],
+ ['em'],
+ ['code'],
+ ['pre'],
+ ['ol'],
+ ['ul'],
+ ['li'],
+ ['table'],
+ ['thead'],
+ ['tr'],
+ ['th'],
+ ['td'],
+ ['img'],
+ ['video'],
+ ['hr'],
+ ]) {
+ selectors = selectors.length === 0 ? [name] : selectors
+
+ let selector =
+ target === 'legacy' ? selectors.map((selector) => `& ${selector}`) : selectors.join(', ')
+
+ addVariant(
+ `${className}-${name}`,
+ target === 'legacy' ? selector : `& :is(${inWhere(selector, options)})`
+ )
+ }
+
+ addComponents(
+ Object.keys(modifiers).map((modifier) => ({
+ [modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`]: configToCss(
+ modifiers[modifier],
+ {
+ target,
+ className,
+ modifier,
+ prefix,
+ }
+ ),
+ }))
+ )
+ }
+ },
+ () => {
+ return {
+ theme: { typography: getStyles(baseFontSize) },
+ }
+ }
+)
diff --git a/packages/ui/style/typo-plugin/utils.js b/packages/ui/style/typo-plugin/utils.js
new file mode 100644
index 00000000..085f72e1
--- /dev/null
+++ b/packages/ui/style/typo-plugin/utils.js
@@ -0,0 +1,62 @@
+const isPlainObject = require('lodash.isplainobject')
+
+const parser = require('postcss-selector-parser')
+const parseSelector = parser()
+
+module.exports = {
+ isUsableColor(color, values) {
+ return isPlainObject(values) && color !== 'gray' && values[600]
+ },
+
+ /**
+ * @param {string} selector
+ */
+ commonTrailingPseudos(selector) {
+ let ast = parseSelector.astSync(selector)
+
+ /** @type {import('postcss-selector-parser').Pseudo[][]} */
+ let matrix = []
+
+ // Put the pseudo elements in reverse order in a sparse, column-major 2D array
+ for (let [i, sel] of ast.nodes.entries()) {
+ for (const [j, child] of [...sel.nodes].reverse().entries()) {
+ // We only care about pseudo elements
+ if (child.type !== 'pseudo' || !child.value.startsWith('::')) {
+ break
+ }
+
+ matrix[j] = matrix[j] || []
+ matrix[j][i] = child
+ }
+ }
+
+ let trailingPseudos = parser.selector()
+
+ // At this point the pseudo elements are in a column-major 2D array
+ // This means each row contains one "column" of pseudo elements from each selector
+ // We can compare all the pseudo elements in a row to see if they are the same
+ for (const pseudos of matrix) {
+ // It's a sparse 2D array so there are going to be holes in the rows
+ // We skip those
+ if (!pseudos) {
+ continue
+ }
+
+ let values = new Set([...pseudos.map((p) => p.value)])
+
+ // The pseudo elements are not the same
+ if (values.size > 1) {
+ break
+ }
+
+ pseudos.forEach((pseudo) => pseudo.remove())
+ trailingPseudos.prepend(pseudos[0])
+ }
+
+ if (trailingPseudos.nodes.length) {
+ return [trailingPseudos.toString(), ast.toString()]
+ }
+
+ return [null, selector]
+ },
+}
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
new file mode 100644
index 00000000..0fed53f1
--- /dev/null
+++ b/packages/ui/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules", "./dist", "**/*.stories.tsx"],
+ "plugins": [{ "name": "next" }],
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "outDir": "./dist",
+ "jsx": "react-jsx",
+ "lib": ["dom", "es2015"]
+ }
+}
diff --git a/packages/ui/types/breakpoints.ts b/packages/ui/types/breakpoints.ts
new file mode 100644
index 00000000..d29244db
--- /dev/null
+++ b/packages/ui/types/breakpoints.ts
@@ -0,0 +1,11 @@
+// @ts-nocheck
+
+import bps from '../style/screens.tailwind'
+
+type Breakpoint = keyof typeof bps
+const Breakpoints = Object.keys(bps)
+
+export {
+ Breakpoints,
+ type Breakpoint
+}
\ No newline at end of file
diff --git a/packages/ui/types/button-def.ts b/packages/ui/types/button-def.ts
new file mode 100644
index 00000000..28d33468
--- /dev/null
+++ b/packages/ui/types/button-def.ts
@@ -0,0 +1,39 @@
+import { type ButtonProps } from '../primitives/button'
+
+type SubmitServerAction = (data: any, enclosure?: any) => Promise
+
+interface ButtonModalProps {
+ open: boolean
+ onOpenChange: (b: boolean) => void
+ buttonText: string
+ buttonProps: ButtonProps
+ title: string
+ byline?: string
+ action: SubmitServerAction
+ actionEnclosure?: any
+}
+
+interface ButtonModalDef {
+ Comp: React.ComponentType
+ title: string
+ props?: any
+ byline?: string
+ action?: SubmitServerAction
+ actionEnclosure?: any
+}
+
+interface ButtonDef {
+ text: string
+ props: ButtonProps
+ action: {
+ type: 'modal' /* | future: other types */
+ def: ButtonModalDef /* | future: other supported specifiers, such as Server Actions, or UI Actions or ? */
+ }
+}
+
+export {
+ type ButtonDef,
+ type SubmitServerAction,
+ type ButtonModalProps,
+ type ButtonModalDef,
+}
diff --git a/packages/ui/types/contact-info.ts b/packages/ui/types/contact-info.ts
new file mode 100644
index 00000000..0333078b
--- /dev/null
+++ b/packages/ui/types/contact-info.ts
@@ -0,0 +1,11 @@
+interface ContactInfo {
+ email: string
+ phone: string
+}
+
+type ContactInfoFields = keyof ContactInfo
+
+export {
+ type ContactInfo,
+ type ContactInfoFields
+}
\ No newline at end of file
diff --git a/packages/ui/types/dimensions.ts b/packages/ui/types/dimensions.ts
new file mode 100644
index 00000000..9ac1a64e
--- /dev/null
+++ b/packages/ui/types/dimensions.ts
@@ -0,0 +1,18 @@
+// From Next Image Dim
+type Dimensions = {
+ w: number | `${number}`
+ h: number | `${number}`
+}
+
+type TShirtDimensions = {
+ sm?: Dimensions,
+ md: Dimensions,
+ lg?: Dimensions
+}
+
+export {
+ type Dimensions,
+ type TShirtDimensions
+}
+
+
\ No newline at end of file
diff --git a/packages/ui/types/icon.ts b/packages/ui/types/icon.ts
new file mode 100644
index 00000000..d2ab5911
--- /dev/null
+++ b/packages/ui/types/icon.ts
@@ -0,0 +1,10 @@
+import type { ReactNode } from 'react'
+
+//import type { LucideIcon} from 'lucide-react'
+//import type { SocialIconProps } from '@/components/icons/social-icon'
+
+type Icon = ReactNode
+
+export {
+ type Icon as default
+}
\ No newline at end of file
diff --git a/packages/ui/types/index.ts b/packages/ui/types/index.ts
new file mode 100644
index 00000000..513dba98
--- /dev/null
+++ b/packages/ui/types/index.ts
@@ -0,0 +1,38 @@
+import {
+ type ButtonDef,
+ type SubmitServerAction,
+ type ButtonModalProps,
+ type ButtonModalDef,
+} from './button-def'
+
+import type LinkDef from './link-def'
+
+import { type Breakpoint, Breakpoints } from './breakpoints'
+
+import type TShirtSize from './t-shirt-size'
+import type { TShirtDimensions, Dimensions } from './dimensions'
+import type Icon from './icon'
+import type SiteConf from './site-conf'
+
+import {
+ type ContactInfo,
+ type ContactInfoFields
+} from './contact-info'
+
+export {
+ type ButtonDef,
+ type Dimensions,
+ type SubmitServerAction,
+ type ButtonModalProps,
+ type ButtonModalDef,
+ type LinkDef,
+ type Icon,
+ type TShirtSize,
+ type Breakpoint,
+ type TShirtDimensions,
+ Breakpoints,
+ type ContactInfo,
+ type ContactInfoFields,
+ type SiteConf,
+}
+
diff --git a/packages/ui/types/link-def.ts b/packages/ui/types/link-def.ts
new file mode 100644
index 00000000..77f08d4f
--- /dev/null
+++ b/packages/ui/types/link-def.ts
@@ -0,0 +1,21 @@
+import type { ButtonVariants, ButtonSizes } from '../primitives/button'
+import type { Icon } from '../types'
+
+
+interface LinkDef {
+ title: string
+ href?: string
+ // external links get target='_blank' by default, unless newTab=false
+ // interal links get no target set by default, unless newTab=true
+ external?: boolean
+ newTab?: boolean
+ disabled?: boolean
+ variant?: ButtonVariants
+ size?: ButtonSizes
+ icon?: Icon
+ iconAfter?: boolean
+}
+
+export {
+ type LinkDef as default
+}
diff --git a/packages/ui/types/site-conf.ts b/packages/ui/types/site-conf.ts
new file mode 100644
index 00000000..c6cb9010
--- /dev/null
+++ b/packages/ui/types/site-conf.ts
@@ -0,0 +1,18 @@
+import type LinkDef from './link-def'
+
+interface SiteConf {
+ title: string
+ template: string
+ desc: string
+ mainNav: {
+ short?: LinkDef[]
+ aux?: LinkDef[]
+ full: LinkDef[]
+ }
+ footer: LinkDef[][]
+ currentAs?: string
+}
+
+export {
+ type SiteConf as default
+}
\ No newline at end of file
diff --git a/packages/ui/types/t-shirt-size.ts b/packages/ui/types/t-shirt-size.ts
new file mode 100644
index 00000000..a28aa268
--- /dev/null
+++ b/packages/ui/types/t-shirt-size.ts
@@ -0,0 +1,5 @@
+type TShirtSize = 'sm' | 'md' | 'lg'
+
+export {
+ type TShirtSize as default
+}
diff --git a/packages/ui/util/classNames.ts b/packages/ui/util/classNames.ts
new file mode 100644
index 00000000..16153de2
--- /dev/null
+++ b/packages/ui/util/classNames.ts
@@ -0,0 +1,3 @@
+export function classNames(...classes: (string | boolean | undefined)[]): string {
+ return classes.filter(Boolean).join(' ')
+}
\ No newline at end of file
diff --git a/packages/ui/util/index.ts b/packages/ui/util/index.ts
new file mode 100644
index 00000000..62030fa2
--- /dev/null
+++ b/packages/ui/util/index.ts
@@ -0,0 +1,66 @@
+import { compiler as mdCompiler } from 'markdown-to-jsx'
+
+import { clsx, type ClassValue } from 'clsx'
+import { twMerge } from 'tailwind-merge'
+import { Dimensions } from '../types'
+
+export const cn = (...inputs: ClassValue[]) => (
+ twMerge(clsx(inputs))
+)
+
+export const DEF_VIDEO_PROPS = {
+ autoPlay: true,
+ loop: true,
+ muted: true,
+ playsInline: true
+}
+
+export const markdown = (s: string, options?: any): JSX.Element => (
+ mdCompiler(s, {
+ wrapper: null,
+ ...options
+ })
+)
+
+export const round = (num: number): string => (
+ num
+ .toFixed(7)
+ .replace(/(\.[0-9]+?)0+$/, '$1')
+ .replace(/\.0$/, '')
+)
+
+export const pxToRem = (px: number, base: number): string => (`${round(px / base)}rem`)
+
+export const pxToEm = (px: number, base: number): string => (`${round(px / base)}em`)
+
+export const hexToRgb = (hex: string): string => {
+ hex = hex.replace('#', '')
+ hex = hex.length === 3 ? hex.replace(/./g, '$&$&') : hex
+ const r = parseInt(hex.substring(0, 2), 16)
+ const g = parseInt(hex.substring(2, 4), 16)
+ const b = parseInt(hex.substring(4, 6), 16)
+ return `${r} ${g} ${b}`
+}
+
+
+export const asNum = (n: number | `${number}`): number => (
+ (typeof n === 'number') ? n : parseInt(n, 10)
+)
+
+ // https://stackoverflow.com/questions/3971841/how-to-resize-images-proportionally-keeping-the-aspect-ratio
+export const constrain = (dim: Dimensions, constraint: Dimensions): Dimensions => {
+ const c = {
+ w: asNum(constraint.w),
+ h: asNum(constraint.h)
+ }
+ const d = {
+ w: asNum(dim.w),
+ h: asNum(dim.h)
+ }
+
+ const ratio = Math.min(c.w / d.w, c.h / d.h)
+ return {
+ w: Math.round(d.w * ratio),
+ h: Math.round(d.h * ratio)
+ }
+}
diff --git a/packages/ui/util/specifier.ts b/packages/ui/util/specifier.ts
new file mode 100644
index 00000000..42e30b44
--- /dev/null
+++ b/packages/ui/util/specifier.ts
@@ -0,0 +1,43 @@
+import type { Dimensions, TShirtSize } from '../types'
+import type { TypographySize } from '../primitives/apply-typography'
+
+export const getPrimaryStartingWith = (s: string, toFind: string) => {
+ const tokenArray = s.split(' ')
+ return tokenArray.find((tok) => (tok.startsWith(`${toFind}-`)))
+}
+
+export const getTShirtSize = (s: string): TShirtSize | undefined => {
+ const subTokenArray = s.split('-')
+ return subTokenArray[subTokenArray.length - 1] as TShirtSize
+}
+
+export const getTypographySize = (s: string): TypographySize | undefined => {
+ const subTokenArray = s.split('-')
+ return subTokenArray[subTokenArray.length - 1] as TypographySize
+}
+
+export const getDim = (s: string): Dimensions | undefined => {
+ const subTokenArray = s.split('-')
+ const dimStr = subTokenArray[subTokenArray.length - 1]
+ if (dimStr) {
+ const dimTokenArray = s.split('x')
+ return dimTokenArray ? {
+ w: Number(dimTokenArray[0]),
+ h: Number(dimTokenArray[1])
+ } : undefined
+ }
+ return undefined
+}
+
+export function getSpecifierData(
+ main: string,
+ getPrimary: (s: string) => string | undefined,
+ getData: (s: string) => T | undefined,
+ def?: T
+): T | undefined {
+ const primary = getPrimary(main)
+ if (primary) {
+ return getData(primary) ?? def
+ }
+ return def ?? undefined
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 53fb2bac..fef31e4a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -443,6 +443,118 @@ importers:
specifier: ^4.9.3
version: 4.9.5
+ packages/ui:
+ dependencies:
+ '@hookform/resolvers':
+ specifier: ^3.3.2
+ version: 3.3.4(react-hook-form@7.49.2)
+ '@radix-ui/react-accordion':
+ specifier: ^1.1.2
+ version: 1.1.2(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-dialog':
+ specifier: ^1.0.5
+ version: 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-label':
+ specifier: ^2.0.2
+ version: 2.0.2(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot':
+ specifier: ^1.0.2
+ version: 1.0.2(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-toast':
+ specifier: ^1.1.5
+ version: 1.1.5(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@tailwindcss/container-queries':
+ specifier: ^0.1.1
+ version: 0.1.1(tailwindcss@3.4.1)
+ class-variance-authority:
+ specifier: ^0.7.0
+ version: 0.7.0
+ clsx:
+ specifier: ^2.1.0
+ version: 2.1.0
+ lodash.castarray:
+ specifier: ^4.4.0
+ version: 4.4.0
+ lodash.isplainobject:
+ specifier: ^4.0.6
+ version: 4.0.6
+ lodash.merge:
+ specifier: ^4.6.2
+ version: 4.6.2
+ lucide-react:
+ specifier: ^0.307.0
+ version: 0.307.0(react@18.2.0)
+ markdown-to-jsx:
+ specifier: ^7.3.2
+ version: 7.4.0(react@18.2.0)
+ next:
+ specifier: ^14.0.4
+ version: 14.0.4(@babel/core@7.22.1)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0)
+ next-themes:
+ specifier: ^0.2.1
+ version: 0.2.1(next@14.0.4)(react-dom@18.2.0)(react@18.2.0)
+ postcss-selector-parser:
+ specifier: ^6.0.13
+ version: 6.0.13
+ react:
+ specifier: ^18.2.0
+ version: 18.2.0
+ react-device-detect:
+ specifier: ^2.2.3
+ version: 2.2.3(react-dom@18.2.0)(react@18.2.0)
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.2.0(react@18.2.0)
+ react-hook-form:
+ specifier: ^7.47.0
+ version: 7.49.2(react@18.2.0)
+ react-social-icons:
+ specifier: ^6.4.0
+ version: 6.7.0(react-dom@18.2.0)(react@18.2.0)
+ tailwind-merge:
+ specifier: ^2.2.0
+ version: 2.2.0
+ tailwindcss:
+ specifier: ^3.4.1
+ version: 3.4.1(ts-node@10.9.1)
+ tailwindcss-animate:
+ specifier: ^1.0.6
+ version: 1.0.6(tailwindcss@3.4.1)
+ tailwindcss-interaction-media:
+ specifier: ^0.1.0
+ version: 0.1.0
+ validator:
+ specifier: ^13.11.0
+ version: 13.11.0
+ zod:
+ specifier: ^3.22.4
+ version: 3.22.4
+ devDependencies:
+ '@types/mdx':
+ specifier: ^2.0.9
+ version: 2.0.10
+ '@types/node':
+ specifier: ^20.10.7
+ version: 20.10.7
+ '@types/react':
+ specifier: ^18.2.47
+ version: 18.2.47
+ '@types/react-dom':
+ specifier: ^18.2.18
+ version: 18.2.18
+ autoprefixer:
+ specifier: ^10.4.16
+ version: 10.4.16(postcss@8.4.33)
+ postcss:
+ specifier: ^8.4.33
+ version: 8.4.33
+ postcss-import:
+ specifier: ^16.0.0
+ version: 16.0.0(postcss@8.4.33)
+ typescript:
+ specifier: ^5.3.3
+ version: 5.3.3
+
templates/next-template:
dependencies:
'@radix-ui/react-slot':
@@ -935,6 +1047,13 @@ packages:
dependencies:
regenerator-runtime: 0.13.11
+ /@babel/runtime@7.23.7:
+ resolution: {integrity: sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ regenerator-runtime: 0.14.1
+ dev: false
+
/@babel/template@7.21.9:
resolution: {integrity: sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==}
engines: {node: '>=6.9.0'}
@@ -1443,7 +1562,7 @@ packages:
ts-pattern: 4.3.0
unified: 10.1.2
yaml: 2.3.1
- zod: 3.21.4
+ zod: 3.22.4
transitivePeerDependencies:
- '@effect-ts/otel-node'
- esbuild
@@ -1874,6 +1993,14 @@ packages:
react-hook-form: 7.44.2(react@18.2.0)
dev: false
+ /@hookform/resolvers@3.3.4(react-hook-form@7.49.2):
+ resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==}
+ peerDependencies:
+ react-hook-form: ^7.0.0
+ dependencies:
+ react-hook-form: 7.49.2(react@18.2.0)
+ dev: false
+
/@humanwhocodes/config-array@0.11.10:
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
engines: {node: '>=10.10.0'}
@@ -2054,7 +2181,7 @@ packages:
resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==}
dependencies:
'@types/estree-jsx': 1.0.0
- '@types/mdx': 2.0.5
+ '@types/mdx': 2.0.10
estree-util-build-jsx: 2.2.2
estree-util-is-identifier-name: 2.1.0
estree-util-to-js: 1.2.0
@@ -2544,6 +2671,35 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-accordion@1.1.2(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-direction': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-accordion@1.1.2(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==}
peerDependencies:
@@ -2693,6 +2849,34 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
peerDependencies:
@@ -2721,6 +2905,30 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies:
@@ -2768,6 +2976,20 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
peerDependencies:
@@ -2817,6 +3039,20 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-context@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-context@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
peerDependencies:
@@ -2892,6 +3128,54 @@ packages:
react-remove-scroll: 2.5.5(@types/react@18.2.7)(react@18.2.0)
dev: false
+ /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ aria-hidden: 1.2.3
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-remove-scroll: 2.5.5(@types/react@18.2.47)(react@18.2.0)
+ dev: false
+
+ /@radix-ui/react-direction@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-direction@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
peerDependencies:
@@ -2947,6 +3231,31 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==}
peerDependencies:
@@ -3008,6 +3317,20 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
peerDependencies:
@@ -3059,6 +3382,29 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==}
peerDependencies:
@@ -3129,6 +3475,21 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-id@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-id@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
peerDependencies:
@@ -3144,6 +3505,27 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-label@2.0.2(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-label@2.0.2(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==}
peerDependencies:
@@ -3395,6 +3777,27 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
peerDependencies:
@@ -3429,6 +3832,28 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
peerDependencies:
@@ -3463,6 +3888,27 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
peerDependencies:
@@ -3712,6 +4158,21 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-slot@1.0.2(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-slot@1.0.2(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
peerDependencies:
@@ -3814,6 +4275,38 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-toast@1.1.5(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==}
peerDependencies:
@@ -3905,6 +4398,20 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
peerDependencies:
@@ -3929,6 +4436,21 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
peerDependencies:
@@ -3954,6 +4476,21 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.6
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
peerDependencies:
@@ -3978,6 +4515,20 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==}
peerDependencies:
@@ -4036,6 +4587,27 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.3
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.18
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
peerDependencies:
@@ -4109,6 +4681,14 @@ packages:
defer-to-connect: 1.1.3
dev: false
+ /@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.1):
+ resolution: {integrity: sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==}
+ peerDependencies:
+ tailwindcss: '>=3.2.0'
+ dependencies:
+ tailwindcss: 3.4.1(ts-node@10.9.1)
+ dev: false
+
/@tanstack/react-table@8.9.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-yHs2m6lk5J5RNcu2dNtsDGux66wNXZjEgzxos6MRCX8gL+nqxeW3ZglqP6eANN0bGElPnjvqiUHGQvdACOr3Cw==}
engines: {node: '>=12'}
@@ -4309,9 +4889,8 @@ packages:
dependencies:
'@types/unist': 2.0.6
- /@types/mdx@2.0.5:
- resolution: {integrity: sha512-76CqzuD6Q7LC+AtbPqrvD9AqsN0k8bsYo2bM2J8pmNldP1aIPAbzUQ7QbobyXL4eLr1wK5x8FZFe8eF/ubRuBg==}
- dev: false
+ /@types/mdx@2.0.10:
+ resolution: {integrity: sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==}
/@types/minimatch@3.0.5:
resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==}
@@ -4331,6 +4910,12 @@ packages:
/@types/node@17.0.45:
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
+ /@types/node@20.10.7:
+ resolution: {integrity: sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==}
+ dependencies:
+ undici-types: 5.26.5
+ dev: true
+
/@types/node@20.2.5:
resolution: {integrity: sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==}
dev: true
@@ -4359,6 +4944,11 @@ packages:
'@types/reactcss': 1.2.6
dev: true
+ /@types/react-dom@18.2.18:
+ resolution: {integrity: sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==}
+ dependencies:
+ '@types/react': 18.2.47
+
/@types/react-dom@18.2.4:
resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==}
dependencies:
@@ -4367,7 +4957,7 @@ packages:
/@types/react-dom@18.2.6:
resolution: {integrity: sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A==}
dependencies:
- '@types/react': 18.2.14
+ '@types/react': 18.2.47
dev: true
/@types/react@18.2.14:
@@ -4377,6 +4967,13 @@ packages:
'@types/scheduler': 0.16.3
csstype: 3.1.2
+ /@types/react@18.2.47:
+ resolution: {integrity: sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==}
+ dependencies:
+ '@types/prop-types': 15.7.5
+ '@types/scheduler': 0.16.3
+ csstype: 3.1.2
+
/@types/react@18.2.7:
resolution: {integrity: sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==}
dependencies:
@@ -4387,7 +4984,7 @@ packages:
/@types/reactcss@1.2.6:
resolution: {integrity: sha512-qaIzpCuXNWomGR1Xq8SCFTtF4v8V27Y6f+b9+bzHiv087MylI/nTCqqdChNeWS7tslgROmYB7yeiruWX7WnqNg==}
dependencies:
- '@types/react': 18.2.7
+ '@types/react': 18.2.47
dev: true
/@types/resolve@1.20.2:
@@ -4833,6 +5430,22 @@ packages:
postcss: 8.4.24
postcss-value-parser: 4.2.0
+ /autoprefixer@10.4.16(postcss@8.4.33):
+ resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+ dependencies:
+ browserslist: 4.22.2
+ caniuse-lite: 1.0.30001576
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.0.0
+ postcss: 8.4.33
+ postcss-value-parser: 4.2.0
+ dev: true
+
/available-typed-arrays@1.0.5:
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
engines: {node: '>= 0.4'}
@@ -4933,6 +5546,17 @@ packages:
node-releases: 2.0.12
update-browserslist-db: 1.0.11(browserslist@4.21.9)
+ /browserslist@4.22.2:
+ resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+ dependencies:
+ caniuse-lite: 1.0.30001576
+ electron-to-chromium: 1.4.623
+ node-releases: 2.0.14
+ update-browserslist-db: 1.0.13(browserslist@4.22.2)
+ dev: true
+
/buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: false
@@ -5038,6 +5662,10 @@ packages:
/caniuse-lite@1.0.30001512:
resolution: {integrity: sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==}
+ /caniuse-lite@1.0.30001576:
+ resolution: {integrity: sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==}
+ dev: true
+
/ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -5137,6 +5765,12 @@ packages:
typescript: 4.9.5
dev: false
+ /class-variance-authority@0.7.0:
+ resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==}
+ dependencies:
+ clsx: 2.0.0
+ dev: false
+
/classnames@2.3.2:
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
dev: false
@@ -5198,6 +5832,16 @@ packages:
engines: {node: '>=6'}
dev: false
+ /clsx@2.0.0:
+ resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /clsx@2.1.0:
+ resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==}
+ engines: {node: '>=6'}
+ dev: false
+
/cmdk@0.2.0(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==}
peerDependencies:
@@ -5789,6 +6433,10 @@ packages:
/electron-to-chromium@1.4.450:
resolution: {integrity: sha512-BLG5HxSELlrMx7dJ2s+8SFlsCtJp37Zpk2VAxyC6CZtbc+9AJeZHfYHbrlSgdXp6saQ8StMqOTEDaBKgA7u1sw==}
+ /electron-to-chromium@1.4.623:
+ resolution: {integrity: sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==}
+ dev: true
+
/embla-carousel-autoplay@8.0.0-rc15(embla-carousel@8.0.0-rc15):
resolution: {integrity: sha512-ABTbDJGNb9jzI9OV2vSpbUvxUA0ELmK0SI3yPm8Haj3ghssS+vElfahoDqp7zuFkWBRih6w3B51oMPKdF5J55A==}
peerDependencies:
@@ -6357,7 +7005,7 @@ packages:
tailwindcss: ^3.3.2
dependencies:
fast-glob: 3.2.12
- postcss: 8.4.24
+ postcss: 8.4.33
tailwindcss: 3.4.0(ts-node@10.9.1)
dev: false
@@ -6368,7 +7016,7 @@ packages:
tailwindcss: ^3.3.2
dependencies:
fast-glob: 3.3.0
- postcss: 8.4.24
+ postcss: 8.4.33
tailwindcss: 3.3.2(ts-node@10.9.1)
dev: true
@@ -6795,6 +7443,10 @@ packages:
/fraction.js@4.2.0:
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
+ /fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+ dev: true
+
/fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: false
@@ -7890,6 +8542,10 @@ packages:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
dev: false
+ /lodash.castarray@4.4.0:
+ resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
+ dev: false
+
/lodash.clone@4.5.0:
resolution: {integrity: sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==}
@@ -8033,6 +8689,14 @@ packages:
react: 18.2.0
dev: false
+ /lucide-react@0.307.0(react@18.2.0):
+ resolution: {integrity: sha512-+vZ+vUiWPZTMnLHURg4aoIaz6NHOWXVVcVd8iLROu1k4LbyjcnHIKmbjXHCmulz7XAYLWRVXzhJJgIr+Aq3vOg==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/magic-string@0.30.0:
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
engines: {node: '>=12'}
@@ -8062,6 +8726,15 @@ packages:
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
dev: true
+ /markdown-to-jsx@7.4.0(react@18.2.0):
+ resolution: {integrity: sha512-zilc+MIkVVXPyTb4iIUTIz9yyqfcWjszGXnwF9K/aiBWcHXFcmdEMTkG01/oQhwSCH7SY1BnG6+ev5BzWmbPrg==}
+ engines: {node: '>= 10'}
+ peerDependencies:
+ react: '>= 0.14.0'
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/markdown-wasm@1.2.0:
resolution: {integrity: sha512-S12OTkyXCkOgI1n1rZY9cg4bK/PGu80Emjpvwp8BEjwCxhPV3yddF0U6+QhCitdBsI1tzWcoeahmW7k0Pq81OA==}
dev: false
@@ -8774,6 +9447,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ /nanoid@3.3.7:
+ resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
/nanoid@4.0.2:
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
engines: {node: ^14 || ^16 || >=18}
@@ -8972,6 +9650,10 @@ packages:
/node-releases@2.0.12:
resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==}
+ /node-releases@2.0.14:
+ resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
+ dev: true
+
/normalize-package-data@2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
@@ -9362,6 +10044,29 @@ packages:
read-cache: 1.0.0
resolve: 1.22.2
+ /postcss-import@15.1.0(postcss@8.4.33):
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+ dependencies:
+ postcss: 8.4.33
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.2
+
+ /postcss-import@16.0.0(postcss@8.4.33):
+ resolution: {integrity: sha512-e77lhVvrD1I2y7dYmBv0k9ULTdArgEYZt97T4w6sFIU5uxIHvDFQlKgUUyY7v7Barj0Yf/zm5A4OquZN7jKm5Q==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+ dependencies:
+ postcss: 8.4.33
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.2
+ dev: true
+
/postcss-js@4.0.1(postcss@8.4.24):
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
engines: {node: ^12 || ^14 || >= 16}
@@ -9371,6 +10076,15 @@ packages:
camelcase-css: 2.0.1
postcss: 8.4.24
+ /postcss-js@4.0.1(postcss@8.4.33):
+ resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.4.33
+
/postcss-load-config@3.1.4(postcss@8.4.24)(ts-node@10.9.1):
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
@@ -9406,6 +10120,23 @@ packages:
ts-node: 10.9.1(@types/node@17.0.45)(typescript@4.9.5)
yaml: 2.3.1
+ /postcss-load-config@4.0.1(postcss@8.4.33)(ts-node@10.9.1):
+ resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
+ engines: {node: '>= 14'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ lilconfig: 2.1.0
+ postcss: 8.4.33
+ ts-node: 10.9.1(@types/node@17.0.45)(typescript@4.9.5)
+ yaml: 2.3.1
+
/postcss-nested@6.0.1(postcss@8.4.24):
resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
engines: {node: '>=12.0'}
@@ -9415,6 +10146,15 @@ packages:
postcss: 8.4.24
postcss-selector-parser: 6.0.13
+ /postcss-nested@6.0.1(postcss@8.4.33):
+ resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+ dependencies:
+ postcss: 8.4.33
+ postcss-selector-parser: 6.0.13
+
/postcss-selector-parser@6.0.13:
resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==}
engines: {node: '>=4'}
@@ -9455,6 +10195,14 @@ packages:
source-map-js: 1.0.2
dev: false
+ /postcss@8.4.33:
+ resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.7
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+
/prebuild-install@7.1.1:
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
engines: {node: '>=10'}
@@ -9608,6 +10356,17 @@ packages:
react: 18.2.0
dev: false
+ /react-device-detect@2.2.3(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw==}
+ peerDependencies:
+ react: '>= 0.14.0'
+ react-dom: '>= 0.14.0'
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ ua-parser-js: 1.0.37
+ dev: false
+
/react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
@@ -9627,6 +10386,15 @@ packages:
react: 18.2.0
dev: false
+ /react-hook-form@7.49.2(react@18.2.0):
+ resolution: {integrity: sha512-TZcnSc17+LPPVpMRIDNVITY6w20deMdNi6iehTFLV1x8SqThXGwu93HjlUVU09pzFgZH7qZOvLMM7UYf2ShAHA==}
+ engines: {node: '>=18', pnpm: '8'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -9638,6 +10406,22 @@ packages:
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
dev: false
+ /react-remove-scroll-bar@2.3.4(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.47
+ react: 18.2.0
+ react-style-singleton: 2.2.1(@types/react@18.2.47)(react@18.2.0)
+ tslib: 2.5.2
+ dev: false
+
/react-remove-scroll-bar@2.3.4(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
engines: {node: '>=10'}
@@ -9673,6 +10457,25 @@ packages:
use-sidecar: 1.1.2(@types/react@18.2.7)(react@18.2.0)
dev: false
+ /react-remove-scroll@2.5.5(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.47
+ react: 18.2.0
+ react-remove-scroll-bar: 2.3.4(@types/react@18.2.47)(react@18.2.0)
+ react-style-singleton: 2.2.1(@types/react@18.2.47)(react@18.2.0)
+ tslib: 2.5.2
+ use-callback-ref: 1.3.0(@types/react@18.2.47)(react@18.2.0)
+ use-sidecar: 1.1.2(@types/react@18.2.47)(react@18.2.0)
+ dev: false
+
/react-remove-scroll@2.5.5(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
engines: {node: '>=10'}
@@ -9727,6 +10530,34 @@ packages:
react-transition-group: 2.9.0(react-dom@18.2.0)(react@18.2.0)
dev: false
+ /react-social-icons@6.7.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-SgqFyhK8CNNkthPDUCVnvb6n/wa2LxAFrFKT7/0aDDXZfEkQkZnfraJnO36YJGL6iXeIchBs8aKam1awbJv9QA==}
+ peerDependencies:
+ react: 15.x.x || 16.x.x || 17.x.x || 18.x.x
+ react-dom: 15.x.x || 16.x.x || 17.x.x || 18.x.x
+ dependencies:
+ '@babel/runtime': 7.23.7
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /react-style-singleton@2.2.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.47
+ get-nonce: 1.0.1
+ invariant: 2.2.4
+ react: 18.2.0
+ tslib: 2.5.2
+ dev: false
+
/react-style-singleton@2.2.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
@@ -9879,6 +10710,10 @@ packages:
/regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
+ /regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+ dev: false
+
/regexp.prototype.flags@1.5.0:
resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==}
engines: {node: '>= 0.4'}
@@ -10689,6 +11524,12 @@ packages:
resolution: {integrity: sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==}
dev: false
+ /tailwind-merge@2.2.0:
+ resolution: {integrity: sha512-SqqhhaL0T06SW59+JVNfAqKdqLs0497esifRrZ7jOaefP3o64fdFNDMrAQWZFMxTLJPiHVjRLUywT8uFz1xNWQ==}
+ dependencies:
+ '@babel/runtime': 7.23.7
+ dev: false
+
/tailwindcss-animate@1.0.5(tailwindcss@3.4.0):
resolution: {integrity: sha512-UU3qrOJ4lFQABY+MVADmBm+0KW3xZyhMdRvejwtXqYOL7YjHYxmuREFAZdmVG5LPe5E9CAst846SLC4j5I3dcw==}
peerDependencies:
@@ -10705,6 +11546,18 @@ packages:
tailwindcss: 3.3.2(ts-node@10.9.1)
dev: false
+ /tailwindcss-animate@1.0.6(tailwindcss@3.4.1):
+ resolution: {integrity: sha512-4WigSGMvbl3gCCact62ZvOngA+PRqhAn7si3TQ3/ZuPuQZcIEtVap+ENSXbzWhpojKB8CpvnIsrwBu8/RnHtuw==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders'
+ dependencies:
+ tailwindcss: 3.4.1(ts-node@10.9.1)
+ dev: false
+
+ /tailwindcss-interaction-media@0.1.0:
+ resolution: {integrity: sha512-3/o9Gc8sXqrUHp/7w4T+Zt46GubrO7BRmxJpB6BogJt0B33K5G1lOVe2q0Ro43Zkk51Tp2ED0zQ/wL5weO+yUw==}
+ dev: false
+
/tailwindcss@3.3.2(ts-node@10.9.1):
resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==}
engines: {node: '>=14.0.0'}
@@ -10724,11 +11577,11 @@ packages:
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.0.0
- postcss: 8.4.24
- postcss-import: 15.1.0(postcss@8.4.24)
- postcss-js: 4.0.1(postcss@8.4.24)
- postcss-load-config: 4.0.1(postcss@8.4.24)(ts-node@10.9.1)
- postcss-nested: 6.0.1(postcss@8.4.24)
+ postcss: 8.4.33
+ postcss-import: 15.1.0(postcss@8.4.33)
+ postcss-js: 4.0.1(postcss@8.4.33)
+ postcss-load-config: 4.0.1(postcss@8.4.33)(ts-node@10.9.1)
+ postcss-nested: 6.0.1(postcss@8.4.33)
postcss-selector-parser: 6.0.13
postcss-value-parser: 4.2.0
resolve: 1.22.2
@@ -10766,6 +11619,37 @@ packages:
transitivePeerDependencies:
- ts-node
+ /tailwindcss@3.4.1(ts-node@10.9.1):
+ resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.5.3
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.0
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.0
+ lilconfig: 2.1.0
+ micromatch: 4.0.5
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.0.0
+ postcss: 8.4.33
+ postcss-import: 15.1.0(postcss@8.4.33)
+ postcss-js: 4.0.1(postcss@8.4.33)
+ postcss-load-config: 4.0.1(postcss@8.4.33)(ts-node@10.9.1)
+ postcss-nested: 6.0.1(postcss@8.4.33)
+ postcss-selector-parser: 6.0.13
+ resolve: 1.22.2
+ sucrase: 3.32.0
+ transitivePeerDependencies:
+ - ts-node
+ dev: false
+
/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
@@ -11192,6 +12076,16 @@ packages:
engines: {node: '>=4.2.0'}
hasBin: true
+ /typescript@5.3.3:
+ resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+ dev: true
+
+ /ua-parser-js@1.0.37:
+ resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==}
+ dev: false
+
/ufo@1.1.2:
resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==}
dev: false
@@ -11204,6 +12098,10 @@ packages:
has-symbols: 1.0.3
which-boxed-primitive: 1.0.2
+ /undici-types@5.26.5:
+ resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+ dev: true
+
/unified@10.1.2:
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
dependencies:
@@ -11297,6 +12195,17 @@ packages:
escalade: 3.1.1
picocolors: 1.0.0
+ /update-browserslist-db@1.0.13(browserslist@4.22.2):
+ resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+ dependencies:
+ browserslist: 4.22.2
+ escalade: 3.1.1
+ picocolors: 1.0.0
+ dev: true
+
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
@@ -11309,6 +12218,21 @@ packages:
prepend-http: 2.0.0
dev: false
+ /use-callback-ref@1.3.0(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.47
+ react: 18.2.0
+ tslib: 2.5.2
+ dev: false
+
/use-callback-ref@1.3.0(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==}
engines: {node: '>=10'}
@@ -11324,6 +12248,22 @@ packages:
tslib: 2.5.2
dev: false
+ /use-sidecar@1.1.2(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.47
+ detect-node-es: 1.1.0
+ react: 18.2.0
+ tslib: 2.5.2
+ dev: false
+
/use-sidecar@1.1.2(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
engines: {node: '>=10'}
@@ -11384,6 +12324,11 @@ packages:
builtins: 1.0.3
dev: false
+ /validator@13.11.0:
+ resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==}
+ engines: {node: '>= 0.10'}
+ dev: false
+
/vaul@0.2.0(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-bzqiLz0RzTOewNFRptP9b9Ri5Zrz9cYoK/Qsmo+cbPBJOjiI/Y7Ch3p3BBZKmsmXcQYSp4FsQme9I5DCHxXfeg==}
peerDependencies:
@@ -11517,7 +12462,7 @@ packages:
dependencies:
'@types/node': 17.0.45
esbuild: 0.17.19
- postcss: 8.4.24
+ postcss: 8.4.33
rollup: 3.23.0
optionalDependencies:
fsevents: 2.3.3
@@ -11842,5 +12787,9 @@ packages:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
dev: false
+ /zod@3.22.4:
+ resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
+ dev: false
+
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 00000000..26857aef
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "incremental": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve"
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index d72a9f3a..0f768906 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,17 +2,24 @@
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
+ "allowJs": true,
"composite": false,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
+ "incremental": true,
"inlineSources": false,
"isolatedModules": true,
+ "jsx": "preserve",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "module": "esnext",
"moduleResolution": "node",
+ "noEmit": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
+ "resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
},