@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,115 @@
|
|
|
1
|
+
import React, { type PropsWithChildren } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { Block, ScreenfulBlock} from '../../def'
|
|
4
|
+
import { containsToken, cn } from '../../../util'
|
|
5
|
+
import ContentComponent from '../content'
|
|
6
|
+
|
|
7
|
+
const ContentColumn: React.FC<{
|
|
8
|
+
blocks: Block[]
|
|
9
|
+
specifiers?: string
|
|
10
|
+
agent?: string
|
|
11
|
+
className?: string
|
|
12
|
+
}> = ({
|
|
13
|
+
blocks,
|
|
14
|
+
specifiers,
|
|
15
|
+
agent,
|
|
16
|
+
className='',
|
|
17
|
+
}) => {
|
|
18
|
+
|
|
19
|
+
const specified = (s: string) => (containsToken(specifiers, s))
|
|
20
|
+
|
|
21
|
+
let modifiers = ''
|
|
22
|
+
|
|
23
|
+
if (agent !== 'phone') {
|
|
24
|
+
if (specified('right')) {
|
|
25
|
+
modifiers += 'items-end '
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
else if (specified('center')) {
|
|
29
|
+
modifiers += 'items-center '
|
|
30
|
+
}
|
|
31
|
+
// default to left
|
|
32
|
+
else {
|
|
33
|
+
modifiers += 'items-start '
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// default to left
|
|
37
|
+
else {
|
|
38
|
+
modifiers += 'items-start '
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (agent !== 'phone') {
|
|
42
|
+
if (specified('bottom')) {
|
|
43
|
+
modifiers += 'justify-end '
|
|
44
|
+
}
|
|
45
|
+
else if (specified('vert-center')) {
|
|
46
|
+
modifiers += 'justify-center '
|
|
47
|
+
}
|
|
48
|
+
// default to top
|
|
49
|
+
else {
|
|
50
|
+
modifiers += 'justify-start '
|
|
51
|
+
}
|
|
52
|
+
// right aligned text looks shitty on mobile
|
|
53
|
+
if (specified('text-align-right')) {
|
|
54
|
+
modifiers += 'text-right '
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
modifiers += 'text-left '
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
modifiers += 'justify-start '
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (agent === 'phone' && specified('mobile-center-headings')) {
|
|
65
|
+
modifiers += 'typography-headings:text-center '
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className={cn('flex flex-col justify-center ' + modifiers, className)} >
|
|
70
|
+
<ContentComponent blocks={blocks} agent={agent} />
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const Content: React.FC<{
|
|
76
|
+
block: ScreenfulBlock
|
|
77
|
+
agent?: string
|
|
78
|
+
className?: string
|
|
79
|
+
}> = ({
|
|
80
|
+
block: b,
|
|
81
|
+
agent,
|
|
82
|
+
className='',
|
|
83
|
+
}) => {
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
const layoutClx = 'flex flex-col gap-6 ' + ((agent !== 'phone') ? ('md:grid md:gap-8 ' + `md:grid-cols-${b.contentColumns.length} `) : '')
|
|
87
|
+
|
|
88
|
+
const orderclx = (columnIndex: number): string => {
|
|
89
|
+
const orderIndex = b.mobileOrder?.indexOf(columnIndex)
|
|
90
|
+
return (orderIndex && orderIndex >= 0) ? `order-${orderIndex} md:order-none` : '' // one-based in flexbox slec
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return b.contentColumns.length == 1 ? (
|
|
94
|
+
<ContentColumn
|
|
95
|
+
blocks={b.contentColumns[0]}
|
|
96
|
+
specifiers={b.columnSpecifiers?.[0]}
|
|
97
|
+
agent={agent}
|
|
98
|
+
className={cn(className)}
|
|
99
|
+
/>
|
|
100
|
+
) : (
|
|
101
|
+
<div className={cn(layoutClx, className)}>
|
|
102
|
+
{b.contentColumns.map((column, index) => (
|
|
103
|
+
<ContentColumn
|
|
104
|
+
blocks={column}
|
|
105
|
+
specifiers={b.columnSpecifiers?.[index]}
|
|
106
|
+
agent={agent}
|
|
107
|
+
className={orderclx(index)}
|
|
108
|
+
key={index}
|
|
109
|
+
/>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default Content
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import dynamic from 'next/dynamic'
|
|
3
|
+
|
|
4
|
+
import type { Block, ScreenfulBlock, VideoBlock } from '../../def'
|
|
5
|
+
import { containsToken, cn } from '../../../util'
|
|
6
|
+
import { ApplyTypography } from '../../../primitives'
|
|
7
|
+
|
|
8
|
+
import Poster from './poster-background'
|
|
9
|
+
import Content from './content'
|
|
10
|
+
const Video = dynamic(() => (import('./video-background')), {ssr: false, loading: () => (<></>)})
|
|
11
|
+
|
|
12
|
+
const ScreenfulComponent: React.FC<{
|
|
13
|
+
block: Block
|
|
14
|
+
agent?: string
|
|
15
|
+
initialInView?: boolean
|
|
16
|
+
snapTile?: boolean
|
|
17
|
+
className?: string
|
|
18
|
+
}> = ({
|
|
19
|
+
block,
|
|
20
|
+
agent,
|
|
21
|
+
initialInView=false,
|
|
22
|
+
snapTile=false,
|
|
23
|
+
className=''
|
|
24
|
+
}) => {
|
|
25
|
+
|
|
26
|
+
if (block.blockType !== 'screenful') {
|
|
27
|
+
return <>screenful block required</>
|
|
28
|
+
}
|
|
29
|
+
const b = block as ScreenfulBlock
|
|
30
|
+
|
|
31
|
+
const hasBannerVideo = (): boolean => (!!b.banner && (typeof b.banner === 'object'))
|
|
32
|
+
|
|
33
|
+
const tileHeight = (agent === 'desktop') ? 'h-full ' : 'h-[100svh] '
|
|
34
|
+
|
|
35
|
+
const specified = (s: string) => (containsToken(b.specifiers, s))
|
|
36
|
+
const narrowGutters = specified('narrow-gutters') // eg, for a table object that is large
|
|
37
|
+
|
|
38
|
+
// content wrapper clx:
|
|
39
|
+
// [
|
|
40
|
+
// positioning,
|
|
41
|
+
// p&m,
|
|
42
|
+
// p&m-modifiers
|
|
43
|
+
// ]
|
|
44
|
+
const cwclx = [
|
|
45
|
+
'z-10 absolute left-0 right-0 top-0 bottom-0 xl:mx-auto max-w-screen-xl overflow-y-hidden ',
|
|
46
|
+
// desktop header: 80px / pt-20
|
|
47
|
+
// mobile header: 44px / pt-11
|
|
48
|
+
narrowGutters ?
|
|
49
|
+
'px-6 lg:px-8 2xl:px-2 pb-6 pt-15 md:pt-26 lg:pt-28 '
|
|
50
|
+
:
|
|
51
|
+
'px-[8vw] xl:px-[1vw] pb-[8vh] pt-[calc(44px+4vh)] md:pt-[calc(80px+6vh)] ',
|
|
52
|
+
|
|
53
|
+
(agent && agent !== 'desktop') ? 'pt-15 sm:pt-17 pb-0 px-3 sm:px-8' : ''
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<section className={cn('h-[100vh]', (snapTile ? 'snap-start snap-always' : ''), className)}>
|
|
58
|
+
<ApplyTypography className={tileHeight + 'w-full flex flex-row justify-center self-stretch'} >
|
|
59
|
+
<Poster banner={b.banner}>
|
|
60
|
+
{hasBannerVideo() && (
|
|
61
|
+
<Video
|
|
62
|
+
block={b.banner! as VideoBlock}
|
|
63
|
+
className='z-0 absolute top-0 left-0 bottom-0 right-0'
|
|
64
|
+
initialInView={initialInView}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
<div className={cn(...cwclx)} >
|
|
68
|
+
<Content block={b} agent={agent} className='w-full'/>
|
|
69
|
+
{b.footer}
|
|
70
|
+
</div>
|
|
71
|
+
</Poster>
|
|
72
|
+
</ApplyTypography>
|
|
73
|
+
</section>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default ScreenfulComponent
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { type PropsWithChildren } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { VideoBlock } from '../../def'
|
|
4
|
+
import { cn } from '../../../util'
|
|
5
|
+
|
|
6
|
+
const Poster: React.FC<{
|
|
7
|
+
banner: VideoBlock | string | undefined,
|
|
8
|
+
className?: string
|
|
9
|
+
} & PropsWithChildren> = ({
|
|
10
|
+
children,
|
|
11
|
+
banner,
|
|
12
|
+
className=''
|
|
13
|
+
}) => (
|
|
14
|
+
banner ? (
|
|
15
|
+
<div
|
|
16
|
+
className={cn('relative', className)}
|
|
17
|
+
style={{
|
|
18
|
+
height: '100%',
|
|
19
|
+
width: '100%',
|
|
20
|
+
backgroundImage: `url(${(typeof banner === 'string') ? banner : banner.poster!})`,
|
|
21
|
+
backgroundSize: 'cover',
|
|
22
|
+
backgroundRepeat: 'no-repeat',
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</div>
|
|
27
|
+
) : (
|
|
28
|
+
<div className={cn('bg-transparent h-full w-full relative', className)}>
|
|
29
|
+
{children}
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
export default Poster
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { useInView } from 'react-intersection-observer'
|
|
5
|
+
|
|
6
|
+
import type { VideoBlock } from '../../def'
|
|
7
|
+
|
|
8
|
+
const VideoBG: React.FC<{
|
|
9
|
+
block: VideoBlock,
|
|
10
|
+
className?: string,
|
|
11
|
+
initialInView: boolean
|
|
12
|
+
}> = ({
|
|
13
|
+
block,
|
|
14
|
+
className='',
|
|
15
|
+
initialInView
|
|
16
|
+
}) => {
|
|
17
|
+
const { ref, inView } = useInView({
|
|
18
|
+
threshold: 0.75,
|
|
19
|
+
initialInView,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return block ? (
|
|
23
|
+
<div ref={ref} className={className}>
|
|
24
|
+
{inView && (
|
|
25
|
+
<video
|
|
26
|
+
autoPlay
|
|
27
|
+
loop
|
|
28
|
+
muted
|
|
29
|
+
style={{
|
|
30
|
+
margin: 0,
|
|
31
|
+
height: '100%',
|
|
32
|
+
width: '100%',
|
|
33
|
+
objectFit: 'cover',
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
{block.sources?.map((src, index) => (
|
|
37
|
+
<source key={index} src={src} />
|
|
38
|
+
))}
|
|
39
|
+
</video>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
) : null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default VideoBG
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { ldMerge } from '../../util'
|
|
4
|
+
|
|
5
|
+
import type { Breakpoint } from '../../types'
|
|
6
|
+
import { SPACE_DEFAULTS , type TWSpaceUnit, type HeadingLevel} from '../def/space-block'
|
|
7
|
+
import type SpaceBlock from '../def/space-block'
|
|
8
|
+
import { ApplyTypography } from '../../primitives'
|
|
9
|
+
|
|
10
|
+
import type BlockComponentProps from './block-component-props'
|
|
11
|
+
|
|
12
|
+
const TAGS = [
|
|
13
|
+
'div',
|
|
14
|
+
'h1',
|
|
15
|
+
'h2',
|
|
16
|
+
'h3',
|
|
17
|
+
'h4',
|
|
18
|
+
'h5',
|
|
19
|
+
'h6',
|
|
20
|
+
] satisfies React.ElementType[]
|
|
21
|
+
|
|
22
|
+
const SpaceBlockComponent: React.FC<BlockComponentProps> = ({
|
|
23
|
+
block,
|
|
24
|
+
className=''
|
|
25
|
+
}) => {
|
|
26
|
+
|
|
27
|
+
if (block && block.blockType !== 'space') {
|
|
28
|
+
return <>space block required</>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const b = block as SpaceBlock
|
|
32
|
+
|
|
33
|
+
// This code path should handle a undefined or empty sizes field.
|
|
34
|
+
if (!b.level) {
|
|
35
|
+
if (typeof b.sizes == 'number') {
|
|
36
|
+
return <div className={`invisible w-[1px] h-${b.sizes}` } />
|
|
37
|
+
}
|
|
38
|
+
const _sizes: {
|
|
39
|
+
[key in (Breakpoint)]?: TWSpaceUnit
|
|
40
|
+
} = {}
|
|
41
|
+
ldMerge(_sizes, SPACE_DEFAULTS, b.sizes)
|
|
42
|
+
|
|
43
|
+
let clx = ''
|
|
44
|
+
for (const [key, value] of Object.entries(_sizes)) {
|
|
45
|
+
// ts brain fart!
|
|
46
|
+
clx += `${key}:h-${value as TWSpaceUnit} `
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (b.test) {
|
|
50
|
+
console.log(clx)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return <div className={'invisible w-[1px] ' + clx} />
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const Tag = TAGS[b.level]
|
|
57
|
+
const heightClx = (b.level === (0 satisfies HeadingLevel as HeadingLevel)) ? 'h-4' : ''
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<ApplyTypography>
|
|
61
|
+
<Tag className={'invisible m-0 ' + heightClx} > </Tag>
|
|
62
|
+
</ApplyTypography>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default SpaceBlockComponent
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useEffect, useLayoutEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import Image from 'next/image'
|
|
5
|
+
|
|
6
|
+
import type { Dimensions, TShirtSize, TShirtDimensions } from '../../types'
|
|
7
|
+
import { constrain, asNum, cn } from '../../util'
|
|
8
|
+
import type { VideoBlock } from '../def'
|
|
9
|
+
import { VideoPlayer } from '../../primitives'
|
|
10
|
+
|
|
11
|
+
import type BlockComponentProps from './block-component-props'
|
|
12
|
+
|
|
13
|
+
const VideoBlockComponent: React.FC<BlockComponentProps & {
|
|
14
|
+
usePoster?: boolean
|
|
15
|
+
size?: TShirtSize
|
|
16
|
+
constraint?: Dimensions
|
|
17
|
+
}> = ({
|
|
18
|
+
block,
|
|
19
|
+
className='',
|
|
20
|
+
agent,
|
|
21
|
+
usePoster=false,
|
|
22
|
+
size='md',
|
|
23
|
+
constraint
|
|
24
|
+
}) => {
|
|
25
|
+
|
|
26
|
+
const [_dim, setDim] = useState<Dimensions | undefined>(undefined)
|
|
27
|
+
|
|
28
|
+
const onResize = () => {
|
|
29
|
+
setDim({
|
|
30
|
+
w: window.innerWidth,
|
|
31
|
+
h: window.innerHeight
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const windowDefined = typeof window !== 'undefined'
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (window && agent === 'desktop') {
|
|
39
|
+
window.addEventListener('resize', onResize)
|
|
40
|
+
return () => window.removeEventListener('resize', onResize)
|
|
41
|
+
}
|
|
42
|
+
}, [windowDefined])
|
|
43
|
+
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
onResize()
|
|
46
|
+
}, [])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if (block.blockType !== 'video') {
|
|
50
|
+
return <>video block required</>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const b = block as VideoBlock
|
|
54
|
+
const ar = asNum(b.dim.md.w) / asNum(b.dim.md.h)
|
|
55
|
+
if (agent === 'phone') {
|
|
56
|
+
if (b.sizing?.mobile?.vw) {
|
|
57
|
+
// serverside, or at least while the video is loading,
|
|
58
|
+
// generate the css for the correctly sized poster image
|
|
59
|
+
if (!_dim) {
|
|
60
|
+
const width = `${b.sizing.mobile.vw}vw`
|
|
61
|
+
return <div className='dummy-div' style={{
|
|
62
|
+
maxWidth: '100%',
|
|
63
|
+
maxHeight: '100%',
|
|
64
|
+
width,
|
|
65
|
+
height: `calc(${width}/${ar})`,
|
|
66
|
+
backgroundImage: `url(${b.poster!})`,
|
|
67
|
+
backgroundSize: 'contain',
|
|
68
|
+
backgroundRepeat: 'no-repeat',
|
|
69
|
+
}} />
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const width = ((b.sizing.mobile.vw / 100) * asNum(_dim.w))
|
|
73
|
+
const dim = {
|
|
74
|
+
h: width / ar,
|
|
75
|
+
w: width
|
|
76
|
+
}
|
|
77
|
+
return ((
|
|
78
|
+
<VideoPlayer
|
|
79
|
+
className={cn('mx-auto', className)}
|
|
80
|
+
sources={b.sources}
|
|
81
|
+
width={dim.w}
|
|
82
|
+
height={dim.h}
|
|
83
|
+
{...b.videoProps}
|
|
84
|
+
/>
|
|
85
|
+
))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (b.sizing?.vh) {
|
|
90
|
+
// serverside, generate the css for the correctly sized poster image
|
|
91
|
+
if (!_dim) {
|
|
92
|
+
const height = `${b.sizing.vh}vh`
|
|
93
|
+
return <div className='dummy-div' style={{
|
|
94
|
+
maxWidth: '100%',
|
|
95
|
+
maxHeight: '100%',
|
|
96
|
+
height: height,
|
|
97
|
+
width: `calc(${height}*${ar})`,
|
|
98
|
+
backgroundImage: `url(${b.poster!})`,
|
|
99
|
+
backgroundSize: 'contain',
|
|
100
|
+
backgroundRepeat: 'no-repeat',
|
|
101
|
+
}} />
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const height = ((b.sizing.vh / 100) * asNum(_dim.h))
|
|
105
|
+
const dim = {
|
|
106
|
+
h: height,
|
|
107
|
+
w: height * ar
|
|
108
|
+
}
|
|
109
|
+
return ((
|
|
110
|
+
<VideoPlayer
|
|
111
|
+
className={className}
|
|
112
|
+
sources={b.sources}
|
|
113
|
+
width={dim.w}
|
|
114
|
+
height={dim.h}
|
|
115
|
+
{...b.videoProps}
|
|
116
|
+
/>
|
|
117
|
+
))
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const videoDims = b.dim as TShirtDimensions
|
|
122
|
+
const dim = ((size && size in videoDims) ? videoDims[size] : videoDims.md) as Dimensions
|
|
123
|
+
const conDim = (constraint ? constrain(dim, constraint) : dim)
|
|
124
|
+
return usePoster ? (
|
|
125
|
+
<Image src={b.poster!} alt='image' width={conDim.w} height={conDim.h} className={className}/>
|
|
126
|
+
) : (
|
|
127
|
+
<VideoPlayer
|
|
128
|
+
className={className}
|
|
129
|
+
sources={b.sources}
|
|
130
|
+
width={conDim.w}
|
|
131
|
+
height={conDim.h}
|
|
132
|
+
{...b.videoProps}
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default VideoBlockComponent
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type Block from './block'
|
|
2
|
+
|
|
3
|
+
import type { BulletItem, GridDef } from '../../types'
|
|
4
|
+
|
|
5
|
+
interface BulletCardsBlock extends Block {
|
|
6
|
+
blockType: 'bullet-cards'
|
|
7
|
+
/**
|
|
8
|
+
* borders-muted-1 / borders-muted-3
|
|
9
|
+
* default: 2
|
|
10
|
+
*/
|
|
11
|
+
specifiers?: string
|
|
12
|
+
grid: GridDef
|
|
13
|
+
cards: BulletItem[]
|
|
14
|
+
/** in px */
|
|
15
|
+
iconSize?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
type BulletCardsBlock as default
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { type Icon } from '../../types'
|
|
4
|
+
|
|
5
|
+
import type CTABlock from './cta-block'
|
|
6
|
+
import type Block from './block'
|
|
7
|
+
import type ImageBlock from './image-block'
|
|
8
|
+
import type VideoBlock from './video-block'
|
|
9
|
+
|
|
10
|
+
interface CardBlock extends Block {
|
|
11
|
+
blockType: 'card'
|
|
12
|
+
specifiers?: string // 'media-left' or 'appear-disabled' or 'no-borders', etc... can be combined
|
|
13
|
+
title?: string
|
|
14
|
+
byline?: string
|
|
15
|
+
icon?: Icon // for title area
|
|
16
|
+
iconAfter?: boolean
|
|
17
|
+
media?: ImageBlock | VideoBlock
|
|
18
|
+
content?: React.ReactNode
|
|
19
|
+
cta?: CTABlock
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
type CardBlock as default
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
import type Block from './block'
|
|
3
|
+
import type CTABlock from './cta-block'
|
|
4
|
+
import type EnhHeadingBlock from './enh-heading-block'
|
|
5
|
+
|
|
6
|
+
interface CarteBlancheBlock extends Block {
|
|
7
|
+
blockType: 'carte-blanche'
|
|
8
|
+
// big-padding
|
|
9
|
+
// no-internal-borders
|
|
10
|
+
// style-ghost (no-internal-borders, no outer border, no padding)
|
|
11
|
+
specifiers?: string
|
|
12
|
+
topContent?: Block[]
|
|
13
|
+
heading?: EnhHeadingBlock
|
|
14
|
+
content?: Block[]
|
|
15
|
+
cta?: CTABlock
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
type CarteBlancheBlock as default
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LinkDef, ButtonDef } from '../../types'
|
|
2
|
+
import type Block from './block'
|
|
3
|
+
|
|
4
|
+
interface CTABlock extends Block {
|
|
5
|
+
blockType: 'cta'
|
|
6
|
+
// fill: fills the parent width with the elements
|
|
7
|
+
// left / right: (>= md) left or right justify the elements (default is center)
|
|
8
|
+
// mobile-2-columns: mobile defaults to rendering each element full width,
|
|
9
|
+
// on it's own line. This renders them in two columns instead.
|
|
10
|
+
// mobile-center-first-if-odd: if (mobile-2-columns) and length is odd,
|
|
11
|
+
// default is to center last
|
|
12
|
+
// mobile-odd-full-width: fills the full two columns w the centered element
|
|
13
|
+
specifiers?: string
|
|
14
|
+
elements: (LinkDef | ButtonDef)[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
type CTABlock as default
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type Icon from '../../types/icon'
|
|
2
|
+
import type Block from './block'
|
|
3
|
+
|
|
4
|
+
// level is heading tag level. 1 renders as <h1>, etc... 0 renders as <p>
|
|
5
|
+
//
|
|
6
|
+
// mb: is bottom margin in tw units. 'mb-1' produces 0.25rem, so a value of 4 would produce 1rem
|
|
7
|
+
// These are dynamically generated from a template string. mb-1 --> mb-12 have been safelisted.
|
|
8
|
+
// Any higher values used would have to explicitly safelisted in tailwind config.
|
|
9
|
+
// The margin will only be applied if the next element down is present.
|
|
10
|
+
// (eg, heading mb applies only if there is a byline.)
|
|
11
|
+
|
|
12
|
+
interface EnhHeadingBlock extends Block {
|
|
13
|
+
blockType: 'enh-heading'
|
|
14
|
+
// tbd: icon-w-heading (if preheading is present),
|
|
15
|
+
// tbd: icon-w-byline (if preheading and heading is present),
|
|
16
|
+
// tbd: icon-above: above first element
|
|
17
|
+
// tbd: icon-to-right: to right of corresponding element or right justified if 'icon-above'
|
|
18
|
+
// mobile-heading-centered
|
|
19
|
+
// left / right / center for preheading and heading (byline stays left)
|
|
20
|
+
// byline-left / byline-right / byline-center
|
|
21
|
+
// preheading-heading-font
|
|
22
|
+
specifiers?: string
|
|
23
|
+
// By default, appears inline to left of first element (preheading or heading)
|
|
24
|
+
// unless indicated otherwise in specifiers
|
|
25
|
+
icon?: Icon | string // ReactNode or url string to asset
|
|
26
|
+
iconSize?: number // if url string, this sets the size
|
|
27
|
+
preheading?: {
|
|
28
|
+
text: string
|
|
29
|
+
level?: number // default: 4
|
|
30
|
+
mb?: number // default: 2
|
|
31
|
+
}
|
|
32
|
+
heading: {
|
|
33
|
+
text: string
|
|
34
|
+
level?: number // default: 1
|
|
35
|
+
mb?: number // default: 2
|
|
36
|
+
}
|
|
37
|
+
byline?: {
|
|
38
|
+
text: string
|
|
39
|
+
level?: number // default: 6
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
type EnhHeadingBlock as default
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type Block from "./block"
|
|
2
|
+
import type GridDef from "../../types/grid-def"
|
|
3
|
+
|
|
4
|
+
interface GridBlock {
|
|
5
|
+
blockType: 'grid'
|
|
6
|
+
specifiers?: string
|
|
7
|
+
grid: GridDef
|
|
8
|
+
/**
|
|
9
|
+
* Ignored if children are supplied to the GridBlockComp
|
|
10
|
+
*/
|
|
11
|
+
cells?: Block[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
type GridBlock as default
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type Block from './block'
|
|
2
|
+
|
|
3
|
+
interface HeadingBlock extends Block {
|
|
4
|
+
blockType: 'heading'
|
|
5
|
+
heading: string
|
|
6
|
+
byline?: string
|
|
7
|
+
level?: number
|
|
8
|
+
bylineLevel?: number
|
|
9
|
+
spaceBetween?: number
|
|
10
|
+
spaceAfter?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
type HeadingBlock as default
|
|
15
|
+
}
|