@luxfi/core 4.3.11
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/components/access-code-input.tsx +71 -0
- package/components/auth/login-panel.tsx +80 -0
- package/components/chat-widget.tsx +77 -0
- package/components/commerce/bag-button.tsx +67 -0
- package/components/commerce/checkout-panel/close-button.tsx +23 -0
- package/components/commerce/checkout-panel/dt-bag-carousel.tsx +36 -0
- package/components/commerce/checkout-panel/dt-checkout-panel.tsx +68 -0
- package/components/commerce/checkout-panel/index.tsx +124 -0
- package/components/commerce/checkout-panel/links-row.tsx +21 -0
- package/components/commerce/checkout-panel/mb-checkout-panel.tsx +51 -0
- package/components/commerce/checkout-panel/steps-indicator.tsx +39 -0
- package/components/commerce/checkout-panel/thank-you.tsx +18 -0
- package/components/commerce/desktop-bag-popup.tsx +78 -0
- package/components/commerce/mobile-bag-drawer.tsx +51 -0
- package/components/commerce/mobile-menu-toggle-button.tsx +35 -0
- package/components/commerce/mobile-nav-menu.tsx +64 -0
- package/components/contact-dialog/contact-form.tsx +112 -0
- package/components/contact-dialog/disclaimer.tsx +13 -0
- package/components/contact-dialog/index.tsx +64 -0
- package/components/copyright.tsx +21 -0
- package/components/footer.tsx +78 -0
- package/components/header/desktop.tsx +54 -0
- package/components/header/index.tsx +26 -0
- package/components/header/mobile.tsx +161 -0
- package/components/header/theme-toggle.tsx +26 -0
- package/components/icons/bag-icon.tsx +10 -0
- package/components/icons/github.tsx +14 -0
- package/components/icons/index.tsx +35 -0
- package/components/icons/lux-logo.tsx +10 -0
- package/components/icons/secure-delivery.tsx +13 -0
- package/components/icons/social-icon.tsx +35 -0
- package/components/icons/social-svg.css +3 -0
- package/components/icons/youtube-logo.tsx +59 -0
- package/components/index.ts +26 -0
- package/components/logo.tsx +81 -0
- package/components/mini-chart/index.tsx +8 -0
- package/components/mini-chart/mini-chart-props.ts +44 -0
- package/components/mini-chart/mini-chart.tsx +85 -0
- package/components/mini-chart/wrapper.tsx +23 -0
- package/components/not-found/index.tsx +27 -0
- package/components/not-found/not-found-content.mdx +5 -0
- package/components/root-layout.tsx +71 -0
- package/components/scripts.tsx +23 -0
- package/conf/index.ts +50 -0
- package/environment.d.ts +6 -0
- package/next/analytics/fpixel.ts +16 -0
- package/next/analytics/google-analytics.ts +14 -0
- package/next/analytics/index.ts +3 -0
- package/next/analytics/pixel-analytics.tsx +55 -0
- package/next/determine-device-mw.ts +16 -0
- package/next/font/get-app-router-font-classes.ts +12 -0
- package/next/font/load-and-return-lux-next-fonts-on-import.ts +67 -0
- package/next/font/local/Druk-Wide-Bold.ttf +0 -0
- package/next/font/local/Druk-Wide-Medium.ttf +0 -0
- package/next/font/next-font-desc.ts +28 -0
- package/next/font/pages-router-font-vars.tsx +18 -0
- package/next/head-metadata/from-next/metadata-types.ts +158 -0
- package/next/head-metadata/from-next/opengraph-types.ts +267 -0
- package/next/head-metadata/from-next/twitter-types.ts +92 -0
- package/next/head-metadata/index.tsx +208 -0
- package/next/index.ts +1 -0
- package/package.json +72 -0
- package/server-actions/firebase-app.ts +14 -0
- package/server-actions/index.ts +5 -0
- package/server-actions/store-contact.ts +51 -0
- package/site-def/footer/community.tsx +67 -0
- package/site-def/footer/company.ts +37 -0
- package/site-def/footer/ecosystem.ts +37 -0
- package/site-def/footer/index.tsx +26 -0
- package/site-def/footer/legal.ts +28 -0
- package/site-def/footer/network.ts +45 -0
- package/site-def/footer/svg/warpcast-logo.svg +12 -0
- package/site-def/index.ts +3 -0
- package/site-def/main-nav.ts +35 -0
- package/site-def/site-def.ts +37 -0
- package/style/lux-colors.css +85 -0
- package/style/lux-global.css +19 -0
- package/tailwind/fontFamily.tailwind.lux.ts +18 -0
- package/tailwind/index.ts +2 -0
- package/tailwind/lux-tw-fonts.ts +40 -0
- package/tailwind/tailwind.config.lux-preset.ts +10 -0
- package/tsconfig.json +10 -0
- package/types/contact-info.ts +11 -0
- package/types/index.ts +1 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
'use client'
|
2
|
+
import React, { useState, useEffect } from 'react'
|
3
|
+
import { useRouter } from 'next/navigation'
|
4
|
+
|
5
|
+
import { X } from 'lucide-react'
|
6
|
+
|
7
|
+
import {
|
8
|
+
Popover,
|
9
|
+
PopoverContent,
|
10
|
+
PopoverTrigger,
|
11
|
+
PopoverClose,
|
12
|
+
} from "@hanzo/ui/primitives"
|
13
|
+
|
14
|
+
import { cn } from '@hanzo/ui/util'
|
15
|
+
import { CartPanel, useCommerce } from '@hanzo/commerce'
|
16
|
+
|
17
|
+
import * as Icons from '../icons'
|
18
|
+
import sendGAEvent from '../../next/analytics/google-analytics'
|
19
|
+
|
20
|
+
const DesktopBagPopup: React.FC<{
|
21
|
+
triggerClx?: string
|
22
|
+
popupClx?: string
|
23
|
+
trigger: React.ReactNode
|
24
|
+
}> = ({
|
25
|
+
triggerClx='',
|
26
|
+
popupClx='',
|
27
|
+
trigger
|
28
|
+
}) => {
|
29
|
+
const cmmc = useCommerce()
|
30
|
+
|
31
|
+
const [bagOpen, setBagOpen] = useState<boolean>(false)
|
32
|
+
const router = useRouter()
|
33
|
+
|
34
|
+
useEffect(() => {
|
35
|
+
if (bagOpen) {
|
36
|
+
sendGAEvent('view_cart', {
|
37
|
+
items: cmmc.cartItems.map((item) => ({
|
38
|
+
item_id: item.sku,
|
39
|
+
item_name: item.title,
|
40
|
+
item_category: item.familyId,
|
41
|
+
price: item.price,
|
42
|
+
quantity: item.quantity
|
43
|
+
})),
|
44
|
+
value: cmmc.cartTotal,
|
45
|
+
currency: 'USD',
|
46
|
+
})
|
47
|
+
}
|
48
|
+
}, [bagOpen])
|
49
|
+
|
50
|
+
return (
|
51
|
+
<Popover open={bagOpen} onOpenChange={setBagOpen}>
|
52
|
+
<PopoverTrigger className={triggerClx}>
|
53
|
+
{trigger}
|
54
|
+
</PopoverTrigger>
|
55
|
+
<PopoverContent sideOffset={28} className={cn('relative flex flex-col p-0 px-4 pb-4 pt-2', popupClx)}>
|
56
|
+
<PopoverClose className='absolute z-above-content right-2 top-2 self-end hover:bg-level-3 text-muted hover:text-accent p-1 rounded-full'><X className='w-5 h-5'/></PopoverClose>
|
57
|
+
<CartPanel
|
58
|
+
handleCheckout={() => {router.push('/checkout')}}
|
59
|
+
className='mt-4 mb-4 border-none py-0 px-4'
|
60
|
+
listClx='rounded-sm'
|
61
|
+
scrollAfter={5}
|
62
|
+
scrollHeightClx='h-[70vh]'
|
63
|
+
itemClx='mt-3'
|
64
|
+
totalClx='sticky px-1 pr-2 -bottom-[1px] bg-level-1'
|
65
|
+
buttonClx='max-w-[220px] flex-none'
|
66
|
+
>
|
67
|
+
<div className='flex flex-row items-center flex-none justify-center '>
|
68
|
+
<Icons.bag className='mr-2 relative w-6 h-7 fill-foreground ' />
|
69
|
+
<p className='font-heading text-foreground text-default'>Your Bag</p>
|
70
|
+
</div>
|
71
|
+
<div className='h-[1px] w-pr-80 bg-muted-3 mx-auto mt-1.5 flex-none'/>
|
72
|
+
</CartPanel>
|
73
|
+
</PopoverContent>
|
74
|
+
</Popover>
|
75
|
+
)
|
76
|
+
}
|
77
|
+
|
78
|
+
export default DesktopBagPopup
|
@@ -0,0 +1,51 @@
|
|
1
|
+
'use client'
|
2
|
+
import React from 'react'
|
3
|
+
|
4
|
+
import { Drawer, DrawerContent} from '@hanzo/ui/primitives'
|
5
|
+
import { cn } from '@hanzo/ui/util'
|
6
|
+
import { CartPanel } from '@hanzo/commerce'
|
7
|
+
|
8
|
+
import BagButton from './bag-button'
|
9
|
+
|
10
|
+
const MobileBagDrawer: React.FC<{
|
11
|
+
open: boolean,
|
12
|
+
setOpen: (b: boolean) => void
|
13
|
+
handleCheckout: () => void
|
14
|
+
className?: string
|
15
|
+
}> = ({
|
16
|
+
open,
|
17
|
+
setOpen,
|
18
|
+
handleCheckout,
|
19
|
+
className='',
|
20
|
+
}) => {
|
21
|
+
|
22
|
+
return (
|
23
|
+
<Drawer open={open} onOpenChange={setOpen}>
|
24
|
+
<DrawerContent className={cn('rounded-t-xl mt-6 pb-12 h-auto', className)} >
|
25
|
+
<CartPanel
|
26
|
+
handleCheckout={handleCheckout}
|
27
|
+
className='mt-4 mb-4 border-none py-0 px-4 w-full '
|
28
|
+
listClx='rounded-sm'
|
29
|
+
scrollAfter={5}
|
30
|
+
itemClx='mt-2'
|
31
|
+
totalClx='sticky px-1 pr-2 border rounded-sm -bottom-[1px] bg-background'
|
32
|
+
buttonClx='max-w-[220px] flex-none'
|
33
|
+
>
|
34
|
+
<div className='flex flex-row items-center flex-none justify-center '>
|
35
|
+
<BagButton
|
36
|
+
noHoverEffects
|
37
|
+
showIfEmpty
|
38
|
+
className=
|
39
|
+
'mr-2 relative w-6 h-7'
|
40
|
+
iconClx='fill-foreground '
|
41
|
+
/>
|
42
|
+
<p className='font-heading text-foreground text-default'>Your Bag</p>
|
43
|
+
</div>
|
44
|
+
<div className='h-[1px] w-pr-80 bg-muted-3 mx-auto mt-1.5 flex-none'/>
|
45
|
+
</CartPanel>
|
46
|
+
</DrawerContent>
|
47
|
+
</Drawer>
|
48
|
+
)
|
49
|
+
}
|
50
|
+
|
51
|
+
export default MobileBagDrawer
|
@@ -0,0 +1,35 @@
|
|
1
|
+
'use client'
|
2
|
+
import React, { useState } from 'react'
|
3
|
+
|
4
|
+
import { Plus } from 'lucide-react'
|
5
|
+
|
6
|
+
import { Button } from '@hanzo/ui/primitives'
|
7
|
+
import { cn } from '@hanzo/ui/util'
|
8
|
+
|
9
|
+
const MobileMenuToggleButton: React.FC<{
|
10
|
+
open: boolean
|
11
|
+
setOpen: (b: boolean) => void
|
12
|
+
className?: string
|
13
|
+
}> = ({
|
14
|
+
open,
|
15
|
+
setOpen,
|
16
|
+
className=''
|
17
|
+
}) => {
|
18
|
+
|
19
|
+
return (
|
20
|
+
<Button
|
21
|
+
variant='ghost'
|
22
|
+
size='default'
|
23
|
+
rounded='full'
|
24
|
+
onClick={() => {setOpen(!open)}}
|
25
|
+
className={cn('p-0 aspect-square hover:bg-background sm:hover:bg-level-1 active:scale-75', className)}
|
26
|
+
>
|
27
|
+
<Plus width={28} height={28} className={
|
28
|
+
'block h-full will-change-transform transition-transform transition-scale transition-duration-[1500] ' +
|
29
|
+
(open ? 'rotate-[135deg] scale-110' : 'rotate-none')
|
30
|
+
} />
|
31
|
+
</Button>
|
32
|
+
)
|
33
|
+
}
|
34
|
+
|
35
|
+
export default MobileMenuToggleButton
|
@@ -0,0 +1,64 @@
|
|
1
|
+
'use client'
|
2
|
+
import React from 'react'
|
3
|
+
|
4
|
+
import type { LinkDef } from '@hanzo/ui/types'
|
5
|
+
import { LinkElement } from '@hanzo/ui/primitives'
|
6
|
+
import { cn } from '@hanzo/ui/util'
|
7
|
+
|
8
|
+
const MobileNav: React.FC<{
|
9
|
+
currentAs: string | undefined
|
10
|
+
links: LinkDef[]
|
11
|
+
className?: string
|
12
|
+
commonItemClx?: string | ((def: LinkDef) => string),
|
13
|
+
/**
|
14
|
+
* This is called *in addition* to the link's actual navigation
|
15
|
+
* action. eg, I link is clicked, and the modal menu is closes
|
16
|
+
*/
|
17
|
+
onAction?: () => void
|
18
|
+
}> = ({
|
19
|
+
currentAs,
|
20
|
+
links,
|
21
|
+
onAction,
|
22
|
+
className='',
|
23
|
+
commonItemClx,
|
24
|
+
}) => (
|
25
|
+
|
26
|
+
links.length > 0 ? (
|
27
|
+
|
28
|
+
<nav className={className} >
|
29
|
+
{links.map((el, index) => {
|
30
|
+
const variant = el.variant ?? 'link'
|
31
|
+
let internalClx = ''
|
32
|
+
// note that linkFG (or any other variant of 'link')
|
33
|
+
// will not get assigned these classes,
|
34
|
+
// and will remain styles is 'foreground' (hence the name)
|
35
|
+
if (variant === 'link') {
|
36
|
+
internalClx+= ' text-muted hover:text-foreground active:text-accent rounded-none'
|
37
|
+
if (currentAs && currentAs === el.href) {
|
38
|
+
internalClx += ' text-accent '
|
39
|
+
}
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
internalClx+= ' min-w-0'
|
43
|
+
}
|
44
|
+
if (currentAs && currentAs === el.href) {
|
45
|
+
internalClx += ' pointer-events-none'
|
46
|
+
}
|
47
|
+
const itemClx = (commonItemClx) ? (typeof commonItemClx === 'string' ? commonItemClx : commonItemClx(el)) : ''
|
48
|
+
|
49
|
+
return (
|
50
|
+
<LinkElement
|
51
|
+
def={el}
|
52
|
+
key={`common-${index}`}
|
53
|
+
size='lg'
|
54
|
+
className={cn(internalClx, itemClx)}
|
55
|
+
onClick={onAction}
|
56
|
+
/>
|
57
|
+
)
|
58
|
+
})}
|
59
|
+
</nav>
|
60
|
+
)
|
61
|
+
: null
|
62
|
+
)
|
63
|
+
|
64
|
+
export default MobileNav
|
@@ -0,0 +1,112 @@
|
|
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 '@hanzo/ui/primitives'
|
22
|
+
|
23
|
+
import type { SubmitServerAction } from '@hanzo/ui/types'
|
24
|
+
import type { ContactInfo } from '../../types'
|
25
|
+
|
26
|
+
const ValidationSchema = z.object({
|
27
|
+
email: z
|
28
|
+
.string()
|
29
|
+
.min(1, { message: "Email must be provided." })
|
30
|
+
.email("Invalid email."),
|
31
|
+
phone: z
|
32
|
+
.string()
|
33
|
+
.min(1, { message: "Telephone must be provided." })
|
34
|
+
.refine(validator.isMobilePhone, { message: "Invalid format." })
|
35
|
+
})
|
36
|
+
|
37
|
+
const ContactForm: React.FC<{
|
38
|
+
onSubmit: SubmitServerAction
|
39
|
+
enclosure: any
|
40
|
+
}> = ({
|
41
|
+
onSubmit,
|
42
|
+
enclosure
|
43
|
+
}) => {
|
44
|
+
|
45
|
+
const form = useForm<ContactInfo>({
|
46
|
+
// @ts-ignore (pnpm linking / tsc bug )
|
47
|
+
resolver: zodResolver(ValidationSchema),
|
48
|
+
defaultValues: {
|
49
|
+
email: '',
|
50
|
+
phone: '',
|
51
|
+
},
|
52
|
+
})
|
53
|
+
|
54
|
+
const [isPending, startTransition] = useTransition()
|
55
|
+
|
56
|
+
const onFormSubmit: SubmitHandler<ContactInfo> = (data) => {
|
57
|
+
// https://github.com/orgs/react-hook-form/discussions/10757#discussioncomment-6672403
|
58
|
+
// @ts-ignore
|
59
|
+
startTransition(async () => {
|
60
|
+
await onSubmit(data, enclosure)
|
61
|
+
})
|
62
|
+
}
|
63
|
+
|
64
|
+
const MyFormItem: React.FC<{
|
65
|
+
field: ControllerRenderProps<ContactInfo, 'email'> | ControllerRenderProps<ContactInfo, 'phone'>
|
66
|
+
placeholder: string
|
67
|
+
}> = ({
|
68
|
+
field,
|
69
|
+
placeholder
|
70
|
+
}) => (
|
71
|
+
<FormItem className="space-y-0" >
|
72
|
+
<FormControl>
|
73
|
+
<Input placeholder={placeholder} {...field} className="mt-0 text-foreground"/>
|
74
|
+
</FormControl>
|
75
|
+
<div className="flex flex-row justify-start items-stretch gap-2">
|
76
|
+
<FormMessage />
|
77
|
+
</div>
|
78
|
+
</FormItem>
|
79
|
+
)
|
80
|
+
|
81
|
+
return (
|
82
|
+
<Form {...form}>
|
83
|
+
<form onSubmit={form.handleSubmit(onFormSubmit)} className="w-3/4">
|
84
|
+
<div className='flex flex-col justify-start items-stretch mt-4'>
|
85
|
+
<FormField
|
86
|
+
control={form.control}
|
87
|
+
name='email'
|
88
|
+
// @ts-ignore
|
89
|
+
render={({ field }) => ( <MyFormItem field={field} placeholder='email'/> )}
|
90
|
+
/>
|
91
|
+
<FormField
|
92
|
+
control={form.control}
|
93
|
+
name='phone'
|
94
|
+
// @ts-ignore
|
95
|
+
render={({ field }) => ( <MyFormItem field={field} placeholder='phone'/> )}
|
96
|
+
/>
|
97
|
+
<Button disabled={isPending} type='submit' className='bg-primary text-primary-fg hover:bg-primary-hover'>
|
98
|
+
{isPending ? (<>
|
99
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
100
|
+
Please wait
|
101
|
+
</>
|
102
|
+
) : (
|
103
|
+
<>Submit</>
|
104
|
+
)}
|
105
|
+
</Button>
|
106
|
+
</div>
|
107
|
+
</form>
|
108
|
+
</Form>
|
109
|
+
)
|
110
|
+
}
|
111
|
+
|
112
|
+
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='/privacy'>Privacy Policy</a> and <a href='/terms'>Terms & conditions</a>.
|
9
|
+
</div>
|
10
|
+
)
|
11
|
+
|
12
|
+
export default Disclaimer
|
13
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
'use client'
|
2
|
+
|
3
|
+
import React, { useState } from 'react'
|
4
|
+
|
5
|
+
import type { ButtonModalProps} from '@hanzo/ui/types'
|
6
|
+
|
7
|
+
import {
|
8
|
+
Button,
|
9
|
+
Dialog,
|
10
|
+
DialogContent,
|
11
|
+
DialogDescription,
|
12
|
+
DialogHeader,
|
13
|
+
DialogTitle,
|
14
|
+
DialogTrigger,
|
15
|
+
} from '@hanzo/ui/primitives'
|
16
|
+
|
17
|
+
import ContactForm from './contact-form'
|
18
|
+
import Disclaimer from './disclaimer'
|
19
|
+
|
20
|
+
const ContactDialog: React.FC<ButtonModalProps> = ({
|
21
|
+
open,
|
22
|
+
onOpenChange,
|
23
|
+
buttonText,
|
24
|
+
buttonProps,
|
25
|
+
title,
|
26
|
+
byline,
|
27
|
+
action,
|
28
|
+
actionEnclosure
|
29
|
+
}) => {
|
30
|
+
const [success, setSuccess] = useState(false)
|
31
|
+
|
32
|
+
const onSubmit = async (formData: any, enclosure: any) => {
|
33
|
+
const result = await action(formData, enclosure)
|
34
|
+
if (result.success) {
|
35
|
+
setSuccess(true)
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
return (
|
40
|
+
<Dialog open={open} onOpenChange={onOpenChange} >
|
41
|
+
<DialogTrigger asChild>
|
42
|
+
<Button {...buttonProps} >{buttonText}</Button>
|
43
|
+
</DialogTrigger>
|
44
|
+
<DialogContent className="sm:max-w-[500px] p-0 gap-0 bg-background border">
|
45
|
+
<DialogHeader className='py-6 text-foreground'>
|
46
|
+
<DialogTitle className='text-4xl text-center text-inherit'>{title}</DialogTitle>
|
47
|
+
{byline && (<DialogDescription className='text-inherit text-lg text-center'>{byline} </DialogDescription>)}
|
48
|
+
</DialogHeader>
|
49
|
+
<div className='px-8 pb-8 rounded-e-lg flex flex-col justify-start items-center'>
|
50
|
+
{success ? (
|
51
|
+
<p>{actionEnclosure?.reply}</p>
|
52
|
+
) : (
|
53
|
+
<ContactForm onSubmit={onSubmit} enclosure={actionEnclosure}/>
|
54
|
+
)}
|
55
|
+
<div className='text-muted-1 text-xs mt-4' >
|
56
|
+
<Disclaimer />
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
</DialogContent>
|
60
|
+
</Dialog>
|
61
|
+
)
|
62
|
+
}
|
63
|
+
|
64
|
+
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,78 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
|
3
|
+
import type { LinkDef } from '@hanzo/ui/types'
|
4
|
+
import { NavItems } from '@hanzo/ui/primitives'
|
5
|
+
import { cn } from '@hanzo/ui/util'
|
6
|
+
|
7
|
+
import Copyright from './copyright'
|
8
|
+
import type { SiteDef } from '../site-def'
|
9
|
+
import { legal } from '../site-def/footer/legal'
|
10
|
+
import Logo from './logo'
|
11
|
+
|
12
|
+
const Footer: React.FC<{
|
13
|
+
siteDef: SiteDef,
|
14
|
+
className?: string,
|
15
|
+
noHorizPadding?: boolean
|
16
|
+
}> = ({
|
17
|
+
siteDef,
|
18
|
+
className='',
|
19
|
+
noHorizPadding=false
|
20
|
+
}) => {
|
21
|
+
|
22
|
+
const { footer, aboveCopyright } = siteDef
|
23
|
+
const smGridCols = Math.floor(footer.length/2)
|
24
|
+
const smGridColsClx = `sm:grid-cols-${smGridCols} `
|
25
|
+
const _aboveCopyright = (typeof aboveCopyright === 'undefined') ? legal : aboveCopyright
|
26
|
+
|
27
|
+
return (
|
28
|
+
<footer className={cn('grow flex flex-col justify-between gap-6 pb-[2vh]', className)}>
|
29
|
+
<div className={
|
30
|
+
(noHorizPadding ? '' : 'px-5 md:px-8 ') +
|
31
|
+
'grid grid-cols-2 gap-4 gap-y-6 md:gap-x-6 lg:gap-8 ' + smGridColsClx +
|
32
|
+
'md:w-full sm:justify-items-center md:mx-0 lg:w-full max-w-screen-2xl ' +
|
33
|
+
'md:flex md:flex-row md:justify-between '
|
34
|
+
}>
|
35
|
+
<div className='hidden lg:flex flex-col' key={0}>
|
36
|
+
<Logo size='md' layout='text-only' />
|
37
|
+
</div>
|
38
|
+
{footer.map((defs: LinkDef[], index: number) => {
|
39
|
+
|
40
|
+
const xsColSpanClx = ((index === footer.length - 1) && (footer.length % 2 === 1)) ?
|
41
|
+
'xs:col-span-2 xs:mx-auto md:col-span-1 md:mx-0 ' : ''
|
42
|
+
|
43
|
+
return (
|
44
|
+
<NavItems
|
45
|
+
items={defs}
|
46
|
+
currentAs={siteDef.currentAs}
|
47
|
+
as='nav'
|
48
|
+
className={cn('sm:min-w-[150px] md:min-w-0 flex flex-col justify-start items-start ' +
|
49
|
+
'gap-[11px] sm:gap-[12px] md:gap-[15px] ',
|
50
|
+
xsColSpanClx
|
51
|
+
)}
|
52
|
+
key={index + 1}
|
53
|
+
itemClx={(def: LinkDef) => ((def.variant === 'linkFG') ?
|
54
|
+
'font-nav text-[15px]/[1.3] font-medium text-foreground tracking-normal'
|
55
|
+
:
|
56
|
+
'text-[15px]/[1.1] font-normal tracking-[0.2px] text-muted-1'
|
57
|
+
)}
|
58
|
+
/>
|
59
|
+
)
|
60
|
+
})}
|
61
|
+
</div>
|
62
|
+
<div className='md:mt-[2vh]'>
|
63
|
+
{_aboveCopyright.length > 0 && (
|
64
|
+
<NavItems
|
65
|
+
items={_aboveCopyright}
|
66
|
+
as='div'
|
67
|
+
className={'flex flex-row justify-center gap-4 mb-2'}
|
68
|
+
itemClx={'text-sm text-center text-muted-2 underline hover:text-foreground'}
|
69
|
+
/>
|
70
|
+
)}
|
71
|
+
<Copyright className='text-sm text-center text-muted-3'/>
|
72
|
+
</div>
|
73
|
+
</footer>
|
74
|
+
)
|
75
|
+
}
|
76
|
+
|
77
|
+
export default Footer
|
78
|
+
// flex flex-col justify-between gap-6
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
|
3
|
+
import { NavItems } from '@hanzo/ui/primitives'
|
4
|
+
import { cn } from '@hanzo/ui/util'
|
5
|
+
import { AuthWidget } from '@hanzo/auth/components'
|
6
|
+
|
7
|
+
import { Logo } from '..'
|
8
|
+
|
9
|
+
import DesktopBagPopup from '../commerce/desktop-bag-popup'
|
10
|
+
import BagButton from '../commerce/bag-button'
|
11
|
+
|
12
|
+
|
13
|
+
import type { LinkDef } from '@hanzo/ui/types'
|
14
|
+
|
15
|
+
const DesktopHeader: React.FC<{
|
16
|
+
currentAs: string | undefined
|
17
|
+
links: LinkDef[]
|
18
|
+
className?: string
|
19
|
+
}> = ({
|
20
|
+
currentAs,
|
21
|
+
links,
|
22
|
+
className = ''
|
23
|
+
}) => {
|
24
|
+
|
25
|
+
// TODO move 13px into a size class and configure twMerge to recognize say, 'text-size-nav'
|
26
|
+
// (vs be beat out by 'text-color-nav')
|
27
|
+
return (
|
28
|
+
<header className={cn('bg-background sticky z-header top-0 ', className)} >
|
29
|
+
{/* md or larger */}
|
30
|
+
<div className={
|
31
|
+
'flex flex-row h-[80px] items-center justify-between ' +
|
32
|
+
'px-[32px] w-full 2xl:mx-auto max-w-screen-2xl'
|
33
|
+
}>
|
34
|
+
<Logo size='md' href='/' className='hidden lg:flex' key='two' layout='text-only'/>
|
35
|
+
<Logo size='sm' href='/' className='hidden md:flex lg:hidden' key='one' layout='text-only'/>
|
36
|
+
{/* md or larger */}
|
37
|
+
<div className='flex gap-4 items-center'>
|
38
|
+
<NavItems
|
39
|
+
currentAs={currentAs}
|
40
|
+
items={links}
|
41
|
+
className='flex md:gap-4 lg:justify-between lg:gap-7'
|
42
|
+
itemClx='font-nav h-8'
|
43
|
+
key='three'
|
44
|
+
/>
|
45
|
+
<DesktopBagPopup popupClx='w-[340px]' trigger={<BagButton className='text-primary -mr-[3px] lg:min-w-0' />} />
|
46
|
+
<AuthWidget/>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
</header>
|
50
|
+
)
|
51
|
+
}
|
52
|
+
|
53
|
+
export default DesktopHeader
|
54
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
|
3
|
+
import type { SiteDef } from '../../site-def'
|
4
|
+
|
5
|
+
import DesktopHeader from './desktop'
|
6
|
+
import MobileHeader from './mobile'
|
7
|
+
import { cn } from '@hanzo/ui/util'
|
8
|
+
|
9
|
+
const Header: React.FC<{
|
10
|
+
siteDef: SiteDef
|
11
|
+
className?: string
|
12
|
+
}> = ({
|
13
|
+
siteDef,
|
14
|
+
className = ''
|
15
|
+
}) => {
|
16
|
+
|
17
|
+
const { nav: { common, featured }, currentAs } = siteDef
|
18
|
+
const links = (featured) ? [...common, ...featured] : common
|
19
|
+
|
20
|
+
return (<>
|
21
|
+
<DesktopHeader className={cn(className, 'hidden md:flex')} links={links} currentAs={currentAs} />
|
22
|
+
<MobileHeader className={cn(className, 'md:hidden')} links={links} currentAs={currentAs} />
|
23
|
+
</>)
|
24
|
+
}
|
25
|
+
|
26
|
+
export default Header
|