@hanzo/ui 0.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/lux-site-icons/android-chrome-192x192.png +0 -0
- package/assets/lux-site-icons/android-chrome-512x512.png +0 -0
- package/assets/lux-site-icons/apple-touch-icon.png +0 -0
- package/assets/lux-site-icons/favicon-16x16.png +0 -0
- package/assets/lux-site-icons/favicon-32x32.png +0 -0
- package/assets/lux-site-icons/favicon.ico +0 -0
- package/assets/standard-docs/LUX-NFT-Terms-and-Conditions.pdf +0 -0
- package/assets/standard-docs/LUX-Privacy-Policy.pdf +0 -0
- package/blocks/components/accordian-block.tsx +48 -0
- package/blocks/components/block-component-props.ts +11 -0
- package/blocks/components/bullet-cards-block.tsx +43 -0
- package/blocks/components/card-block.tsx +213 -0
- package/blocks/components/carte-blanche-block/index.tsx +98 -0
- package/blocks/components/content.tsx +70 -0
- package/blocks/components/cta-block.tsx +98 -0
- package/blocks/components/enh-heading-block.tsx +194 -0
- package/blocks/components/grid-block/grid-block-mutator.ts +12 -0
- package/blocks/components/grid-block/index.tsx +83 -0
- package/blocks/components/grid-block/mutator-registry.ts +10 -0
- package/blocks/components/grid-block/table-borders.mutator.ts +47 -0
- package/blocks/components/group-block.tsx +83 -0
- package/blocks/components/heading-block.tsx +88 -0
- package/blocks/components/image-block.tsx +108 -0
- package/blocks/components/index.ts +30 -0
- package/blocks/components/screenful-block/content.tsx +115 -0
- package/blocks/components/screenful-block/index.tsx +77 -0
- package/blocks/components/screenful-block/poster-background.tsx +34 -0
- package/blocks/components/screenful-block/video-background.tsx +45 -0
- package/blocks/components/space-block.tsx +66 -0
- package/blocks/components/video-block.tsx +137 -0
- package/blocks/def/accordian-block.ts +14 -0
- package/blocks/def/block.ts +7 -0
- package/blocks/def/bullet-cards-block.ts +20 -0
- package/blocks/def/card-block.ts +24 -0
- package/blocks/def/carte-blanche-block.ts +20 -0
- package/blocks/def/cta-block.ts +19 -0
- package/blocks/def/element-block.ts +11 -0
- package/blocks/def/enh-heading-block.ts +45 -0
- package/blocks/def/grid-block.ts +16 -0
- package/blocks/def/group-block.ts +11 -0
- package/blocks/def/heading-block.ts +15 -0
- package/blocks/def/image-block.ts +36 -0
- package/blocks/def/index.ts +35 -0
- package/blocks/def/screenful-block.ts +51 -0
- package/blocks/def/space-block.ts +64 -0
- package/blocks/def/video-block.ts +28 -0
- package/blocks/index.ts +2 -0
- package/common/chat-widget.tsx +75 -0
- package/common/contact-dialog/contact-form.tsx +111 -0
- package/common/contact-dialog/disclaimer.tsx +13 -0
- package/common/contact-dialog/index.tsx +48 -0
- package/common/copyright.tsx +21 -0
- package/common/drawer-menu.tsx +51 -0
- package/common/footer.tsx +77 -0
- package/common/head-metadata/from-next/metadata-types.ts +158 -0
- package/common/head-metadata/from-next/opengraph-types.ts +267 -0
- package/common/head-metadata/from-next/twitter-types.ts +92 -0
- package/common/head-metadata/index.tsx +208 -0
- package/common/header/index.tsx +57 -0
- package/common/header/mobile-nav.tsx +72 -0
- package/common/header/theme-toggle.tsx +26 -0
- package/common/icons/github.tsx +14 -0
- package/common/icons/index.tsx +34 -0
- package/common/icons/lux-logo.tsx +10 -0
- package/common/icons/secure-delivery.tsx +13 -0
- package/common/icons/social-icon.tsx +35 -0
- package/common/icons/youtube-logo.tsx +59 -0
- package/common/index.ts +14 -0
- package/common/logo.tsx +71 -0
- package/common/mini-chart/index.tsx +8 -0
- package/common/mini-chart/mini-chart-props.ts +44 -0
- package/common/mini-chart/mini-chart.tsx +76 -0
- package/common/mini-chart/wrapper.tsx +23 -0
- package/context-providers/index.ts +1 -0
- package/context-providers/theme-provider.tsx +20 -0
- package/next/README.md +11 -0
- package/next/determine-device-middleware.ts +16 -0
- package/next/fonts/DrukTextWide-Bold-Trial.otf +0 -0
- package/next/fonts/DrukTextWide-Heavy-Trial.otf +0 -0
- package/next/fonts/DrukTextWide-Medium-Trial.otf +0 -0
- package/next/get-app-router-font-classes.ts +12 -0
- package/next/load-and-return-lux-next-fonts-on-import.ts +68 -0
- package/next/next-font-desc.ts +28 -0
- package/next/not-found-content.mdx +4 -0
- package/next/not-found.tsx +23 -0
- package/next/pages-router-font-vars.tsx +18 -0
- package/next/root-layout.tsx +53 -0
- package/package.json +105 -0
- package/primitives/accordion.tsx +61 -0
- package/primitives/action-button.tsx +46 -0
- package/primitives/apply-typography.tsx +55 -0
- package/primitives/avatar.tsx +49 -0
- package/primitives/badge.tsx +36 -0
- package/primitives/button.tsx +73 -0
- package/primitives/calendar.tsx +72 -0
- package/primitives/card.tsx +83 -0
- package/primitives/checkbox.tsx +32 -0
- package/primitives/command.tsx +155 -0
- package/primitives/dialog-video-controller.tsx +38 -0
- package/primitives/dialog.tsx +152 -0
- package/primitives/form.tsx +179 -0
- package/primitives/index.ts +144 -0
- package/primitives/inline-icon.tsx +37 -0
- package/primitives/input.tsx +30 -0
- package/primitives/label.tsx +28 -0
- package/primitives/link-element.tsx +104 -0
- package/primitives/main.tsx +17 -0
- package/primitives/mdx-link.tsx +22 -0
- package/primitives/nav-items.tsx +48 -0
- package/primitives/popover.tsx +35 -0
- package/primitives/progress.tsx +27 -0
- package/primitives/scroll-area.tsx +47 -0
- package/primitives/select.tsx +169 -0
- package/primitives/separator.tsx +29 -0
- package/primitives/sheet.tsx +175 -0
- package/primitives/skeleton.tsx +15 -0
- package/primitives/switch.tsx +33 -0
- package/primitives/table.tsx +117 -0
- package/primitives/tabs.tsx +60 -0
- package/primitives/tailwind-indicator.tsx +19 -0
- package/primitives/text-area.tsx +26 -0
- package/primitives/toast.tsx +129 -0
- package/primitives/toaster.tsx +37 -0
- package/primitives/use-toast.ts +192 -0
- package/primitives/video-player.tsx +26 -0
- package/primitives/youtube-embed.tsx +83 -0
- package/siteDef/footer/community.tsx +67 -0
- package/siteDef/footer/company.ts +37 -0
- package/siteDef/footer/ecosystem.ts +37 -0
- package/siteDef/footer/index.tsx +26 -0
- package/siteDef/footer/legal.ts +28 -0
- package/siteDef/footer/network.ts +33 -0
- package/siteDef/footer/svg/warpcast-logo.svg +12 -0
- package/siteDef/main-nav.ts +35 -0
- package/style/globals.css +13 -0
- package/style/hanzo-common.css +32 -0
- package/style/hanzo-default-colors.css +79 -0
- package/style/social-svg.css +3 -0
- package/tailwind/colors.tailwind.js +46 -0
- package/tailwind/fonts.tailwind.ts +31 -0
- package/tailwind/index.ts +18 -0
- package/tailwind/lux-tw-fonts.ts +32 -0
- package/tailwind/safelist.tailwind.js +26 -0
- package/tailwind/screens.tailwind.js +8 -0
- package/tailwind/spacing.tailwind.js +57 -0
- package/tailwind/tailwind.config.base.js +905 -0
- package/tailwind/tw-font-desc.ts +15 -0
- package/tailwind/typo-plugin/get-plugin-styles.js +676 -0
- package/tailwind/typo-plugin/index.d.ts +9 -0
- package/tailwind/typo-plugin/index.js +141 -0
- package/tailwind/typo-plugin/utils.js +60 -0
- package/tailwind/typography-test.mdx +36 -0
- package/types/breakpoints.ts +11 -0
- package/types/bullet-item.ts +10 -0
- package/types/button-def.ts +39 -0
- package/types/contact-info.ts +11 -0
- package/types/dimensions.ts +20 -0
- package/types/grid-def.ts +37 -0
- package/types/icon.ts +10 -0
- package/types/image-def.ts +28 -0
- package/types/index.ts +29 -0
- package/types/link-def.ts +59 -0
- package/types/site-def.ts +31 -0
- package/types/t-shirt-size.ts +5 -0
- package/util/index.ts +76 -0
- package/util/specifier.ts +43 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ImageDef } from '../../types'
|
|
2
|
+
import type Block from './block'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* See {@link ImageDef}
|
|
6
|
+
* see https://nextjs.org/docs/app/api-reference/components/image
|
|
7
|
+
* as well as React.ImgHTMLAttributes.
|
|
8
|
+
*/
|
|
9
|
+
interface ImageBlock extends Block, ImageDef {
|
|
10
|
+
blockType: 'image'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Alignement: 'left' (default) / 'right' / 'center' (must be in flex-col parent)
|
|
14
|
+
* 'mobile-no-scale': By default, scales to 3/4 height (mobile and w < 'md')
|
|
15
|
+
* 'mobile-full-width': Overrides dim, etc. and renders full width (maintaining aspect ratio)
|
|
16
|
+
*/
|
|
17
|
+
specifiers?: string
|
|
18
|
+
/** @deprecated Please use 'mobile-full-width' in specifiers */
|
|
19
|
+
fullWidthOnMobile?: boolean
|
|
20
|
+
/** Next props */
|
|
21
|
+
props?: {
|
|
22
|
+
sizes?: string
|
|
23
|
+
/** if true, any alignement specifiers are ignored */
|
|
24
|
+
fill?: boolean
|
|
25
|
+
style?: {
|
|
26
|
+
objectFit?: string
|
|
27
|
+
objectPosition?: string
|
|
28
|
+
width?: number | string
|
|
29
|
+
height?: number | string
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
type ImageBlock as default,
|
|
36
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type AccordianBlock from './accordian-block'
|
|
2
|
+
import type Block from './block'
|
|
3
|
+
import type BulletCardsBlock from './bullet-cards-block'
|
|
4
|
+
import type CardBlock from './card-block'
|
|
5
|
+
import type CarteBlancheBlock from './carte-blanche-block'
|
|
6
|
+
import type CTABlock from './cta-block'
|
|
7
|
+
import type ElementBlock from './element-block'
|
|
8
|
+
import type GridBlock from './grid-block'
|
|
9
|
+
import type GroupBlock from './group-block'
|
|
10
|
+
import type EnhHeadingBlock from './enh-heading-block'
|
|
11
|
+
import type HeadingBlock from './heading-block'
|
|
12
|
+
import type ImageBlock from './image-block'
|
|
13
|
+
import type VideoBlock from './video-block'
|
|
14
|
+
import type SpaceBlock from './space-block'
|
|
15
|
+
import { SPACE_DEFAULTS } from './space-block'
|
|
16
|
+
import type ScreenfulBlock from './screenful-block'
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
type AccordianBlock,
|
|
20
|
+
type Block,
|
|
21
|
+
type BulletCardsBlock,
|
|
22
|
+
type CardBlock,
|
|
23
|
+
type CarteBlancheBlock,
|
|
24
|
+
type CTABlock,
|
|
25
|
+
type ElementBlock,
|
|
26
|
+
type GridBlock,
|
|
27
|
+
type GroupBlock,
|
|
28
|
+
type HeadingBlock,
|
|
29
|
+
type EnhHeadingBlock,
|
|
30
|
+
type ImageBlock,
|
|
31
|
+
type VideoBlock,
|
|
32
|
+
type SpaceBlock,
|
|
33
|
+
type ScreenfulBlock,
|
|
34
|
+
SPACE_DEFAULTS
|
|
35
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import type Block from './block'
|
|
4
|
+
import type VideoBlock from './video-block'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A common screenful of content
|
|
8
|
+
* An optional banner image or video
|
|
9
|
+
* Content can be in columns
|
|
10
|
+
*/
|
|
11
|
+
interface ScreenfulBlock extends Block {
|
|
12
|
+
blockType: 'screenful'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Either an image URL, or a Video Block
|
|
17
|
+
*
|
|
18
|
+
* If VideoBlock, then it's poster will be rendered server-side
|
|
19
|
+
* and the <video> component will be lazy-loaded client-side.
|
|
20
|
+
*
|
|
21
|
+
* If in a scrolling situation, the video will autoplay when 75% in view
|
|
22
|
+
*/
|
|
23
|
+
banner?: string | VideoBlock
|
|
24
|
+
|
|
25
|
+
/** Specifies rendering and layout hints and variants for block as a whole */
|
|
26
|
+
specifiers?: string
|
|
27
|
+
|
|
28
|
+
/** Specifies rendering and layout hints and variants for corresponding column */
|
|
29
|
+
columnSpecifiers?: string[]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Mobile: The order in the single column mobile layout in which the columns appear.
|
|
33
|
+
* Overrides column order.
|
|
34
|
+
* eg, if you want the second tile of three at the top: [1, 0, 2]
|
|
35
|
+
*/
|
|
36
|
+
mobileOrder?: number[]
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Content Blocks for 1-3 columns.
|
|
40
|
+
* (More than that is allowed, but impractical at many resolutions!)
|
|
41
|
+
* If > 1, it will be enclosed in a 'grid grid-cols-<x>' div.
|
|
42
|
+
*/
|
|
43
|
+
contentColumns: Block[][]
|
|
44
|
+
|
|
45
|
+
/** optional footer element below the grid */
|
|
46
|
+
footer?: ReactNode
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
type ScreenfulBlock as default
|
|
51
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Breakpoint } from '../../types'
|
|
2
|
+
|
|
3
|
+
import type Block from './block'
|
|
4
|
+
|
|
5
|
+
type TWSpaceUnit = number // TODO, pull from tw conf
|
|
6
|
+
type HeadingLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
7
|
+
|
|
8
|
+
const SPACE_DEFAULTS = {
|
|
9
|
+
xs: 2,
|
|
10
|
+
sm: 4,
|
|
11
|
+
md: 5,
|
|
12
|
+
lg: 6,
|
|
13
|
+
xl: 8
|
|
14
|
+
} satisfies {
|
|
15
|
+
[key in (Breakpoint)]?: TWSpaceUnit
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
interface SpaceBlock extends Block {
|
|
20
|
+
blockType: 'space'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* TW units of vertical space, applied at Breakpoints
|
|
24
|
+
* or
|
|
25
|
+
* if just a number, that number at all Breakpoints
|
|
26
|
+
*
|
|
27
|
+
* default {
|
|
28
|
+
* xs: 2,
|
|
29
|
+
* sm: 4,
|
|
30
|
+
* md: 5,
|
|
31
|
+
* lg: 6,
|
|
32
|
+
* xl: 8
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* Any provided values will be merge w the defaults
|
|
36
|
+
* And applied as if they were tw classes in ascending
|
|
37
|
+
* order.
|
|
38
|
+
*
|
|
39
|
+
* impl: <div className='invisible w-[1px] xs:h-<xsval> sm:h-<smval> etc...' />
|
|
40
|
+
*/
|
|
41
|
+
sizes?: {
|
|
42
|
+
[key in (Breakpoint)]?: TWSpaceUnit
|
|
43
|
+
} | TWSpaceUnit
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Heading levels. Gives the vertical space that the corresponding
|
|
47
|
+
* h tag would give.
|
|
48
|
+
* default: 3 (height of <h3>)
|
|
49
|
+
* 0 = 1rem (plus any gaps),
|
|
50
|
+
* For example, 1 = <h1 style={visibility: hidden}> </h1>
|
|
51
|
+
* As <ApplyTypography> would render it, plus any gap.
|
|
52
|
+
*/
|
|
53
|
+
level?: HeadingLevel
|
|
54
|
+
|
|
55
|
+
test?: boolean
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
type SpaceBlock as default,
|
|
61
|
+
type TWSpaceUnit,
|
|
62
|
+
type HeadingLevel,
|
|
63
|
+
SPACE_DEFAULTS
|
|
64
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TShirtDimensions } from '../../types'
|
|
2
|
+
import type Block from './block'
|
|
3
|
+
|
|
4
|
+
interface VideoBlock extends Block {
|
|
5
|
+
blockType: 'video'
|
|
6
|
+
videoProps?: any // For example,
|
|
7
|
+
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
8
|
+
videoProps: {
|
|
9
|
+
autoPlay: true,
|
|
10
|
+
loop: true,
|
|
11
|
+
muted: true,
|
|
12
|
+
playsInline: true
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
Valueless props are boolean.
|
|
16
|
+
NOTE: Must be camalCase as per React conventions! (playsinline => playsInline)
|
|
17
|
+
~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
18
|
+
|
|
19
|
+
poster?: string
|
|
20
|
+
sources?: string[]
|
|
21
|
+
dim: TShirtDimensions
|
|
22
|
+
// These are suppored so far: { vh: 60, mobile: {vw: 70} }
|
|
23
|
+
sizing?: any
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
type VideoBlock as default,
|
|
28
|
+
}
|
package/blocks/index.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { X } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import LuxLogo from './icons/lux-logo'
|
|
7
|
+
import { Button, Card } from '../primitives'
|
|
8
|
+
|
|
9
|
+
const ChatWidget: React.FC<{
|
|
10
|
+
title: string,
|
|
11
|
+
chatbotUrl: string,
|
|
12
|
+
subtitle?: string,
|
|
13
|
+
question?: string,
|
|
14
|
+
/*
|
|
15
|
+
Currently supports these icons from remix icons (https://remixicon.com/):
|
|
16
|
+
GlobalLineIcon,
|
|
17
|
+
ShieldFlashLineIcon,
|
|
18
|
+
BankCardLineIcon,
|
|
19
|
+
GroupLineIcon,
|
|
20
|
+
QuestionnaireLineIcon
|
|
21
|
+
*/
|
|
22
|
+
suggestedQuestions?: { heading: string, message: string, icon?: string }[]
|
|
23
|
+
}> = ({
|
|
24
|
+
title,
|
|
25
|
+
chatbotUrl,
|
|
26
|
+
subtitle,
|
|
27
|
+
question,
|
|
28
|
+
suggestedQuestions
|
|
29
|
+
}) => {
|
|
30
|
+
|
|
31
|
+
const [showChatbot, setShowChatbot] = React.useState<boolean>(false)
|
|
32
|
+
|
|
33
|
+
const onClick = () => { setShowChatbot(!showChatbot) }
|
|
34
|
+
|
|
35
|
+
const searchParams = new URLSearchParams()
|
|
36
|
+
if (question) {
|
|
37
|
+
searchParams.append('question', question)
|
|
38
|
+
}
|
|
39
|
+
if (suggestedQuestions) {
|
|
40
|
+
searchParams.append('sQuestions', suggestedQuestions.map(({ message }) => message).join(','))
|
|
41
|
+
searchParams.append('sHeadings', suggestedQuestions.map(({ heading }) => heading).join(','))
|
|
42
|
+
searchParams.append('sIcons', suggestedQuestions.map(({ icon }) => icon).join(','))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const iframeSrc = `${chatbotUrl}?${searchParams.toString()}`
|
|
46
|
+
|
|
47
|
+
return (<>
|
|
48
|
+
<div className={
|
|
49
|
+
'fixed bottom-0 sm:bottom-20 right-0 w-full h-full ' +
|
|
50
|
+
'sm:max-w-[400px] sm:max-h-[550px] sm:px-4 z-[1001] ' +
|
|
51
|
+
(showChatbot ? 'flex' : 'hidden')
|
|
52
|
+
}>
|
|
53
|
+
<Card className='flex flex-col h-full w-full'>
|
|
54
|
+
<div className='flex px-4 py-2 h-12 bg-level-0 items-center justify-between'>
|
|
55
|
+
<h3 className='font-semibold font-heading'>{title} <span className='opacity-60'>{subtitle}</span></h3>
|
|
56
|
+
<Button onClick={onClick} variant='link' size='icon' className='w-fit sm:hidden'>
|
|
57
|
+
<X />
|
|
58
|
+
</Button>
|
|
59
|
+
</div>
|
|
60
|
+
<iframe src={iframeSrc} className='h-full' />
|
|
61
|
+
</Card>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<Button
|
|
65
|
+
variant='outline'
|
|
66
|
+
size='link'
|
|
67
|
+
onClick={onClick}
|
|
68
|
+
className='fixed bottom-4 right-0 h-12 w-12 mx-4 rounded-full'
|
|
69
|
+
>
|
|
70
|
+
{showChatbot ? <X /> : <LuxLogo width={24} height={24} className='mt-2' />}
|
|
71
|
+
</Button>
|
|
72
|
+
</>)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default ChatWidget
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useTransition } from 'react'
|
|
4
|
+
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
6
|
+
import { useForm, type SubmitHandler, type ControllerRenderProps } from 'react-hook-form'
|
|
7
|
+
import * as z from 'zod'
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import validator from 'validator'
|
|
10
|
+
|
|
11
|
+
import { Loader2 } from 'lucide-react'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Button,
|
|
15
|
+
Input,
|
|
16
|
+
Form,
|
|
17
|
+
FormControl,
|
|
18
|
+
FormField,
|
|
19
|
+
FormItem,
|
|
20
|
+
FormMessage,
|
|
21
|
+
} from '../../primitives'
|
|
22
|
+
|
|
23
|
+
import type { ContactInfo, SubmitServerAction } from '../../types'
|
|
24
|
+
|
|
25
|
+
const ValidationSchema = z.object({
|
|
26
|
+
email: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(1, { message: "Email must be provided." })
|
|
29
|
+
.email("Invalid email."),
|
|
30
|
+
phone: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1, { message: "Telephone must be provided." })
|
|
33
|
+
.refine(validator.isMobilePhone, { message: "Invalid format." })
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const ContactForm: React.FC<{
|
|
37
|
+
onSubmit: SubmitServerAction
|
|
38
|
+
enclosure: any
|
|
39
|
+
}> = ({
|
|
40
|
+
onSubmit,
|
|
41
|
+
enclosure
|
|
42
|
+
}) => {
|
|
43
|
+
|
|
44
|
+
const form = useForm<ContactInfo>({
|
|
45
|
+
// @ts-ignore (pnpm linking / tsc bug )
|
|
46
|
+
resolver: zodResolver(ValidationSchema),
|
|
47
|
+
defaultValues: {
|
|
48
|
+
email: '',
|
|
49
|
+
phone: '',
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const [isPending, startTransition] = useTransition()
|
|
54
|
+
|
|
55
|
+
const onFormSubmit: SubmitHandler<ContactInfo> = (data) => {
|
|
56
|
+
// https://github.com/orgs/react-hook-form/discussions/10757#discussioncomment-6672403
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
startTransition(async () => {
|
|
59
|
+
await onSubmit(data, enclosure)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const MyFormItem: React.FC<{
|
|
64
|
+
field: ControllerRenderProps<ContactInfo, 'email'> | ControllerRenderProps<ContactInfo, 'phone'>
|
|
65
|
+
placeholder: string
|
|
66
|
+
}> = ({
|
|
67
|
+
field,
|
|
68
|
+
placeholder
|
|
69
|
+
}) => (
|
|
70
|
+
<FormItem className="space-y-0" >
|
|
71
|
+
<FormControl>
|
|
72
|
+
<Input placeholder={placeholder} {...field} className="mt-0 text-foreground"/>
|
|
73
|
+
</FormControl>
|
|
74
|
+
<div className="flex flex-row justify-start items-stretch gap-2">
|
|
75
|
+
<FormMessage />
|
|
76
|
+
</div>
|
|
77
|
+
</FormItem>
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Form {...form}>
|
|
82
|
+
<form onSubmit={form.handleSubmit(onFormSubmit)} className="w-3/4">
|
|
83
|
+
<div className='flex flex-col justify-start items-stretch mt-4'>
|
|
84
|
+
<FormField
|
|
85
|
+
control={form.control}
|
|
86
|
+
name='email'
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
render={({ field }) => ( <MyFormItem field={field} placeholder='email'/> )}
|
|
89
|
+
/>
|
|
90
|
+
<FormField
|
|
91
|
+
control={form.control}
|
|
92
|
+
name='phone'
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
render={({ field }) => ( <MyFormItem field={field} placeholder='phone'/> )}
|
|
95
|
+
/>
|
|
96
|
+
<Button disabled={isPending} type='submit' className='bg-primary text-primary-fg hover:bg-primary-hover'>
|
|
97
|
+
{isPending ? (<>
|
|
98
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
99
|
+
Please wait
|
|
100
|
+
</>
|
|
101
|
+
) : (
|
|
102
|
+
<>Submit</>
|
|
103
|
+
)}
|
|
104
|
+
</Button>
|
|
105
|
+
</div>
|
|
106
|
+
</form>
|
|
107
|
+
</Form>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default ContactForm
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
const Disclaimer: React.FC = () => (
|
|
4
|
+
<div>
|
|
5
|
+
By entering your mobile number and submitting, you consent to receive text messages from Lux at the number provided.
|
|
6
|
+
Message and data rates may apply. Message frequency varies.
|
|
7
|
+
You can unsubscribe at any time by replying STOP.
|
|
8
|
+
View our <a href='/assets/standard-docs/LUX-Privacy-Policy.pdf'>Privacy Policy</a> and <a href='/assets/standard-docs/LUX-NFT-Terms-and-Conditions.pdf'>Terms & conditions</a>.
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
export default Disclaimer
|
|
13
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import type { ButtonModalProps} from '../../types'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogDescription,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
DialogTrigger,
|
|
14
|
+
} from '../../primitives'
|
|
15
|
+
|
|
16
|
+
import ContactForm from './contact-form'
|
|
17
|
+
import Disclaimer from './disclaimer'
|
|
18
|
+
|
|
19
|
+
const ContactDialog: React.FC<ButtonModalProps> = ({
|
|
20
|
+
open,
|
|
21
|
+
onOpenChange,
|
|
22
|
+
buttonText,
|
|
23
|
+
buttonProps,
|
|
24
|
+
title,
|
|
25
|
+
byline,
|
|
26
|
+
action,
|
|
27
|
+
actionEnclosure
|
|
28
|
+
}) => (
|
|
29
|
+
<Dialog open={open} onOpenChange={onOpenChange} >
|
|
30
|
+
<DialogTrigger asChild>
|
|
31
|
+
<Button {...buttonProps} >{buttonText}</Button>
|
|
32
|
+
</DialogTrigger>
|
|
33
|
+
<DialogContent className="sm:max-w-[500px] p-0 gap-0 light-theme">
|
|
34
|
+
<DialogHeader className='py-6 text-foreground'>
|
|
35
|
+
<DialogTitle className='text-4xl font-heading text-center text-inherit'>{title}</DialogTitle>
|
|
36
|
+
{byline && (<DialogDescription className='text-inherit text-xl text-center'>{byline} </DialogDescription>)}
|
|
37
|
+
</DialogHeader>
|
|
38
|
+
<div className='p-8 rounded-e-lg flex flex-col justify-start items-center'>
|
|
39
|
+
<ContactForm onSubmit={action} enclosure={actionEnclosure}/>
|
|
40
|
+
<div className='text-muted-1 text-xs mt-4' >
|
|
41
|
+
<Disclaimer />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</DialogContent>
|
|
45
|
+
</Dialog>
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
export default ContactDialog
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
const FIRST = 2020
|
|
4
|
+
|
|
5
|
+
const Copyright: React.FC<{
|
|
6
|
+
className?: string
|
|
7
|
+
}> = ({
|
|
8
|
+
className=''
|
|
9
|
+
}) => {
|
|
10
|
+
|
|
11
|
+
const year = new Date().getFullYear()
|
|
12
|
+
const yearString = (year > FIRST) ? `${FIRST} - ${year}` : FIRST.toString()
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className={className}>
|
|
16
|
+
{`Copyright © ${yearString}`} <br className='sm:hidden'/>Lux Partners Ltd. <br className='md:hidden'/> All rights reserved.
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default Copyright
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { type PropsWithChildren, type ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
import { X as LucideX} from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import Logo from './logo'
|
|
7
|
+
import { Sheet, SheetContent, SheetTrigger } from '../primitives/sheet'
|
|
8
|
+
|
|
9
|
+
const DrawerMenu: React.FC<PropsWithChildren & {
|
|
10
|
+
trigger: ReactNode
|
|
11
|
+
className?: string
|
|
12
|
+
showLogo?: boolean
|
|
13
|
+
}> = ({
|
|
14
|
+
trigger,
|
|
15
|
+
children,
|
|
16
|
+
className='',
|
|
17
|
+
showLogo=true
|
|
18
|
+
}) => {
|
|
19
|
+
|
|
20
|
+
const [open, setOpen] = React.useState(false)
|
|
21
|
+
|
|
22
|
+
const onAction = () => { setOpen(false) }
|
|
23
|
+
|
|
24
|
+
// https://stackoverflow.com/a/49052730/11645689
|
|
25
|
+
const updatedChildren = React.Children.map(
|
|
26
|
+
children,
|
|
27
|
+
(child) => (React.cloneElement(
|
|
28
|
+
child as any, { onAction }
|
|
29
|
+
))
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Sheet open={open} onOpenChange={setOpen} >
|
|
34
|
+
<SheetTrigger>
|
|
35
|
+
{trigger}
|
|
36
|
+
</SheetTrigger>
|
|
37
|
+
<SheetContent
|
|
38
|
+
side="right"
|
|
39
|
+
className={className}
|
|
40
|
+
closeButtonClass='text-inherit opacity-90'
|
|
41
|
+
onClick={onAction}
|
|
42
|
+
closeElement={<LucideX className='h-6 w-6 text-inherit'/>}
|
|
43
|
+
centerElement={showLogo ? <Logo size='xs' className='' /> : null}
|
|
44
|
+
>
|
|
45
|
+
{updatedChildren}
|
|
46
|
+
</SheetContent>
|
|
47
|
+
</Sheet>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default DrawerMenu
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import type { LinkDef, SiteDef } from '../types'
|
|
4
|
+
import { Copyright } from '.'
|
|
5
|
+
import { NavItems } from '../primitives'
|
|
6
|
+
import { legal } from '../siteDef/footer/legal'
|
|
7
|
+
|
|
8
|
+
import Logo from './logo'
|
|
9
|
+
import { cn } from '../util'
|
|
10
|
+
|
|
11
|
+
const Footer: React.FC<{
|
|
12
|
+
siteDef: SiteDef,
|
|
13
|
+
className?: string,
|
|
14
|
+
noHorizPadding?: boolean
|
|
15
|
+
}> = ({
|
|
16
|
+
siteDef,
|
|
17
|
+
className='',
|
|
18
|
+
noHorizPadding=false
|
|
19
|
+
}) => {
|
|
20
|
+
|
|
21
|
+
const { footer, aboveCopyright } = siteDef
|
|
22
|
+
const smGridCols = Math.floor(footer.length/2)
|
|
23
|
+
const smGridColsClx = `sm:grid-cols-${smGridCols} `
|
|
24
|
+
const _aboveCopyright = (typeof aboveCopyright === 'undefined') ? legal : aboveCopyright
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<footer className={cn('grow flex flex-col justify-between gap-6 pb-[2vh]', className)}>
|
|
28
|
+
<div className={
|
|
29
|
+
(noHorizPadding ? '' : 'px-5 md:px-8 ') +
|
|
30
|
+
'grid grid-cols-2 gap-4 gap-y-6 md:gap-x-6 lg:gap-8 ' + smGridColsClx +
|
|
31
|
+
'md:w-full sm:justify-items-center md:mx-0 lg:w-full max-w-screen-2xl ' +
|
|
32
|
+
'md:flex md:flex-row md:justify-between '
|
|
33
|
+
}>
|
|
34
|
+
<div className='hidden lg:flex flex-col' key={0}>
|
|
35
|
+
<Logo size='md' />
|
|
36
|
+
</div>
|
|
37
|
+
{footer.map((defs: LinkDef[], index) => {
|
|
38
|
+
|
|
39
|
+
const xsColSpanClx = ((index === footer.length - 1) && (footer.length % 2 === 1)) ?
|
|
40
|
+
'xs:col-span-2 xs:mx-auto md:col-span-1 md:mx-0 ' : ''
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<NavItems
|
|
44
|
+
items={defs}
|
|
45
|
+
currentAs={siteDef.currentAs}
|
|
46
|
+
as='nav'
|
|
47
|
+
className={cn('sm:min-w-[150px] md:min-w-0 flex flex-col justify-start items-start ' +
|
|
48
|
+
'gap-[11px] sm:gap-[12px] md:gap-[15px] ',
|
|
49
|
+
xsColSpanClx
|
|
50
|
+
)}
|
|
51
|
+
key={index + 1}
|
|
52
|
+
itemClx={(def: LinkDef) => ((def.variant === 'linkFG') ?
|
|
53
|
+
'font-heading text-[15px]/[1.3] font-medium text-foreground tracking-normal'
|
|
54
|
+
:
|
|
55
|
+
'text-[15px]/[1.1] font-normal tracking-[0.2px] text-muted-1'
|
|
56
|
+
)}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
})}
|
|
60
|
+
</div>
|
|
61
|
+
<div className='md:mt-[2vh]'>
|
|
62
|
+
{_aboveCopyright.length > 0 && (
|
|
63
|
+
<NavItems
|
|
64
|
+
items={_aboveCopyright}
|
|
65
|
+
as='div'
|
|
66
|
+
className={'flex flex-row justify-center gap-4 mb-2'}
|
|
67
|
+
itemClx={'text-sm text-center text-muted-2 underline hover:text-foreground'}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
<Copyright className='text-sm text-center text-muted-3'/>
|
|
71
|
+
</div>
|
|
72
|
+
</footer>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default Footer
|
|
77
|
+
// flex flex-col justify-between gap-6
|