@hanzo/ui 3.1.1 → 3.2.0
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/blocks/components/image-block.tsx +17 -14
- package/blocks/components/video-block.tsx +8 -7
- package/package.json +1 -1
- package/primitives/carousel.tsx +7 -2
- package/primitives/image.tsx +73 -0
- package/primitives/index.ts +3 -0
- package/primitives/label.tsx +1 -1
- package/primitives/radio-group.tsx +17 -5
- package/types/dimensions.ts +9 -15
- package/types/image-def.ts +3 -2
- package/types/index.ts +2 -1
- package/types/tshirt-dimensions.ts +20 -0
- package/types/video-def.ts +1 -1
- package/util/index.ts +9 -9
|
@@ -3,18 +3,18 @@ import Image from 'next/image'
|
|
|
3
3
|
|
|
4
4
|
import type { Dimensions } from '../../types'
|
|
5
5
|
import type { ImageBlock } from '../def'
|
|
6
|
-
import {
|
|
6
|
+
import { resolveDimensions, containsToken, cn } from '../../util'
|
|
7
7
|
|
|
8
8
|
import type BlockComponentProps from './block-component-props'
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
const ImageBlockComponent: React.FC<BlockComponentProps & {
|
|
12
|
-
|
|
12
|
+
constraintTo?: {w: number, h: number}
|
|
13
13
|
}> = ({
|
|
14
14
|
block,
|
|
15
15
|
className='',
|
|
16
16
|
agent,
|
|
17
|
-
|
|
17
|
+
constraintTo
|
|
18
18
|
}) => {
|
|
19
19
|
|
|
20
20
|
if (block.blockType !== 'image') {
|
|
@@ -25,8 +25,8 @@ const ImageBlockComponent: React.FC<BlockComponentProps & {
|
|
|
25
25
|
src,
|
|
26
26
|
alt,
|
|
27
27
|
dim,
|
|
28
|
-
ar,
|
|
29
28
|
props,
|
|
29
|
+
sizes,
|
|
30
30
|
fullWidthOnMobile,
|
|
31
31
|
svgFillClass,
|
|
32
32
|
specifiers
|
|
@@ -36,9 +36,9 @@ const ImageBlockComponent: React.FC<BlockComponentProps & {
|
|
|
36
36
|
|
|
37
37
|
const toSpread: any = {}
|
|
38
38
|
if (props?.fill === undefined) {
|
|
39
|
-
const
|
|
40
|
-
toSpread.width =
|
|
41
|
-
toSpread.height =
|
|
39
|
+
const resolved = resolveDimensions(dim, constraintTo)
|
|
40
|
+
toSpread.width = resolved.w
|
|
41
|
+
toSpread.height = resolved.h
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
let _alt: string
|
|
@@ -66,9 +66,10 @@ const ImageBlockComponent: React.FC<BlockComponentProps & {
|
|
|
66
66
|
sizes: '100vw',
|
|
67
67
|
}
|
|
68
68
|
// only for aspect ratio and to satisfy parser
|
|
69
|
-
|
|
70
|
-
toSpread.
|
|
71
|
-
|
|
69
|
+
const resolved = resolveDimensions(dim)
|
|
70
|
+
toSpread.width = resolved.w
|
|
71
|
+
toSpread.height = resolved.h
|
|
72
|
+
|
|
72
73
|
return (
|
|
73
74
|
<div className='flex flex-col items-center w-full'>
|
|
74
75
|
<Image src={src} alt={_alt} {...toSpread} className={cn(_svgFillClass, className)}/>
|
|
@@ -83,13 +84,15 @@ const ImageBlockComponent: React.FC<BlockComponentProps & {
|
|
|
83
84
|
props.style.width = props.style.width *.75
|
|
84
85
|
}
|
|
85
86
|
else if (props?.style && !props?.style.width) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
87
|
+
const resolved = resolveDimensions(dim)
|
|
88
|
+
toSpread.width = resolved.w * .75
|
|
89
|
+
toSpread.height = resolved.w * .75
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
if (sizes) {
|
|
94
|
+
toSpread.sizes = sizes
|
|
95
|
+
}
|
|
93
96
|
|
|
94
97
|
const right = containsToken(specifiers, 'right')
|
|
95
98
|
const center = containsToken(specifiers, 'center')
|
|
@@ -13,17 +13,17 @@ import type BlockComponentProps from './block-component-props'
|
|
|
13
13
|
const VideoBlockComponent: React.FC<BlockComponentProps & {
|
|
14
14
|
usePoster?: boolean
|
|
15
15
|
size?: TShirtSize
|
|
16
|
-
|
|
16
|
+
constrainTo?: {w: number, h: number}
|
|
17
17
|
}> = ({
|
|
18
18
|
block,
|
|
19
19
|
className='',
|
|
20
20
|
agent,
|
|
21
21
|
usePoster=false,
|
|
22
22
|
size='md',
|
|
23
|
-
|
|
23
|
+
constrainTo
|
|
24
24
|
}) => {
|
|
25
25
|
|
|
26
|
-
const [_dim, setDim] = useState<
|
|
26
|
+
const [_dim, setDim] = useState<{w: number, h: number} | undefined>(undefined)
|
|
27
27
|
|
|
28
28
|
const onResize = () => {
|
|
29
29
|
setDim({
|
|
@@ -69,7 +69,7 @@ const VideoBlockComponent: React.FC<BlockComponentProps & {
|
|
|
69
69
|
}} />
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
-
const width = ((b.sizing.mobile.vw / 100) *
|
|
72
|
+
const width = ((b.sizing.mobile.vw / 100) * _dim.w)
|
|
73
73
|
const dim = {
|
|
74
74
|
h: width / ar,
|
|
75
75
|
w: width
|
|
@@ -101,7 +101,8 @@ const VideoBlockComponent: React.FC<BlockComponentProps & {
|
|
|
101
101
|
}} />
|
|
102
102
|
}
|
|
103
103
|
else {
|
|
104
|
-
|
|
104
|
+
|
|
105
|
+
const height = ((b.sizing.vh / 100) * _dim.h)
|
|
105
106
|
const dim = {
|
|
106
107
|
h: height,
|
|
107
108
|
w: height * ar
|
|
@@ -119,8 +120,8 @@ const VideoBlockComponent: React.FC<BlockComponentProps & {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
const videoDims = b.dim as TShirtDimensions
|
|
122
|
-
const dim = ((size && size in videoDims) ? videoDims[size] : videoDims.md) as
|
|
123
|
-
const conDim = (
|
|
123
|
+
const dim = ((size && size in videoDims) ? videoDims[size] : videoDims.md) as {w: number, h: number}
|
|
124
|
+
const conDim = (constrainTo ? constrain(dim, constrainTo) : dim)
|
|
124
125
|
return usePoster ? (
|
|
125
126
|
<Image src={b.poster!} alt='image' width={conDim.w} height={conDim.h} className={className}/>
|
|
126
127
|
) : (
|
package/package.json
CHANGED
package/primitives/carousel.tsx
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import useEmblaCarousel, { type
|
|
4
|
+
import useEmblaCarousel, { type EmblaOptionsType, type EmblaCarouselType } from 'embla-carousel-react'
|
|
5
5
|
import Autoplay from 'embla-carousel-autoplay'
|
|
6
6
|
import { ArrowLeft, ArrowRight } from 'lucide-react'
|
|
7
7
|
|
|
8
8
|
import { cn } from '../util'
|
|
9
9
|
import Button from './button'
|
|
10
10
|
|
|
11
|
-
type CarouselApi =
|
|
11
|
+
type CarouselApi = EmblaCarouselType
|
|
12
12
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
|
13
13
|
type CarouselOptions = UseCarouselParameters[0]
|
|
14
14
|
type CarouselPlugins = UseCarouselParameters[1]
|
|
@@ -18,6 +18,7 @@ type CarouselProps = {
|
|
|
18
18
|
plugins?: CarouselPlugins
|
|
19
19
|
orientation?: 'horizontal' | 'vertical'
|
|
20
20
|
setApi?: (api: CarouselApi) => void
|
|
21
|
+
onSelect?: (api: CarouselApi) => void
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
type CarouselContextProps = {
|
|
@@ -53,6 +54,7 @@ const Carousel = React.forwardRef<
|
|
|
53
54
|
plugins,
|
|
54
55
|
className,
|
|
55
56
|
children,
|
|
57
|
+
onSelect: _onSelect,
|
|
56
58
|
...props
|
|
57
59
|
},
|
|
58
60
|
ref
|
|
@@ -74,6 +76,9 @@ const Carousel = React.forwardRef<
|
|
|
74
76
|
|
|
75
77
|
setCanScrollPrev(api.canScrollPrev())
|
|
76
78
|
setCanScrollNext(api.canScrollNext())
|
|
79
|
+
if (_onSelect) {
|
|
80
|
+
_onSelect(api)
|
|
81
|
+
}
|
|
77
82
|
}, [])
|
|
78
83
|
|
|
79
84
|
const scrollPrev = React.useCallback(() => {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import NextImage from 'next/image'
|
|
3
|
+
|
|
4
|
+
import type { ImageDef } from '../types'
|
|
5
|
+
import { resolveDimensions, cn } from '../util'
|
|
6
|
+
|
|
7
|
+
const Image: React.FC<{
|
|
8
|
+
def: ImageDef
|
|
9
|
+
constrainTo?: {w: number, h: number}
|
|
10
|
+
fullWidth?: boolean
|
|
11
|
+
className?: string
|
|
12
|
+
preload?: boolean
|
|
13
|
+
}> = ({
|
|
14
|
+
def,
|
|
15
|
+
constrainTo,
|
|
16
|
+
fullWidth=false,
|
|
17
|
+
className='',
|
|
18
|
+
preload=false
|
|
19
|
+
}) => {
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
src,
|
|
23
|
+
alt: _alt,
|
|
24
|
+
dim,
|
|
25
|
+
sizes,
|
|
26
|
+
svgFillClass: _svgFillClass,
|
|
27
|
+
} = def
|
|
28
|
+
|
|
29
|
+
const toSpread: any = {}
|
|
30
|
+
if (fullWidth) {
|
|
31
|
+
toSpread.style = {
|
|
32
|
+
width: '100%',
|
|
33
|
+
height: 'auto',
|
|
34
|
+
}
|
|
35
|
+
if (constrainTo) {
|
|
36
|
+
toSpread.style.maxWidth = constrainTo.w
|
|
37
|
+
toSpread.style.maxHeight = constrainTo.h
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const resolved = resolveDimensions(dim, constrainTo)
|
|
42
|
+
toSpread.width = resolved.w
|
|
43
|
+
toSpread.height = resolved.h
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (sizes) {
|
|
47
|
+
toSpread.sizes = sizes
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Note: cannot be spread (a Next thing)
|
|
51
|
+
let alt: string
|
|
52
|
+
if (_alt) {
|
|
53
|
+
alt = _alt
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const tokens = src.split('/')
|
|
57
|
+
// Something remotely meaningful
|
|
58
|
+
alt = (tokens.length > 0) ? tokens[tokens.length - 1] : src
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const svgFillClass = _svgFillClass ?? ''
|
|
62
|
+
|
|
63
|
+
return (fullWidth) ? (
|
|
64
|
+
<div className='relative flex flex-col items-center w-full'>
|
|
65
|
+
<NextImage src={src} alt={alt} {...toSpread} priority={preload} loading={preload ? 'eager' : 'lazy'} className={cn(svgFillClass, className)}/>
|
|
66
|
+
</div>
|
|
67
|
+
) : (
|
|
68
|
+
<NextImage src={src} alt={alt} {...toSpread} priority={preload} loading={preload ? 'eager' : 'lazy'} className={cn(svgFillClass, className)}/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default Image
|
|
73
|
+
|
package/primitives/index.ts
CHANGED
|
@@ -173,6 +173,7 @@ export { default as Switch } from './switch'
|
|
|
173
173
|
export { default as TextArea } from './text-area'
|
|
174
174
|
export { default as Calendar } from './calendar'
|
|
175
175
|
export { default as Checkbox } from './checkbox'
|
|
176
|
+
export { default as Image } from './image'
|
|
176
177
|
export { default as Progress } from './progress'
|
|
177
178
|
export { default as Separator } from './separator'
|
|
178
179
|
export { default as Skeleton } from './skeleton'
|
|
@@ -183,3 +184,5 @@ export { default as ListBox } from './list-box'
|
|
|
183
184
|
export { default as StepIndicator } from './step-indicator'
|
|
184
185
|
|
|
185
186
|
export * as Icons from './icons'
|
|
187
|
+
|
|
188
|
+
|
package/primitives/label.tsx
CHANGED
|
@@ -9,7 +9,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
9
9
|
import { cn } from "../util"
|
|
10
10
|
|
|
11
11
|
const labelVariants = cva(
|
|
12
|
-
"
|
|
12
|
+
"font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
const Label = React.forwardRef<
|
|
@@ -22,19 +22,31 @@ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
|
|
22
22
|
|
|
23
23
|
const RadioGroupItem = React.forwardRef<
|
|
24
24
|
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
|
25
|
-
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
|
26
|
-
|
|
25
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> & {
|
|
26
|
+
larger?: boolean
|
|
27
|
+
}
|
|
28
|
+
>(({
|
|
29
|
+
larger=false,
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}, ref) => {
|
|
27
33
|
return (
|
|
28
34
|
<RadioGroupPrimitive.Item
|
|
29
35
|
ref={ref}
|
|
30
36
|
className={cn(
|
|
31
|
-
|
|
37
|
+
larger ? 'h-5 w-5' : 'h-4 w-4',
|
|
38
|
+
'aspect-square rounded-full border border-muted text-foreground ring-offset-background ' ,
|
|
39
|
+
'focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
40
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
32
41
|
className
|
|
33
42
|
)}
|
|
34
43
|
{...props}
|
|
35
44
|
>
|
|
36
|
-
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
|
37
|
-
<
|
|
45
|
+
<RadioGroupPrimitive.Indicator className="w-full h-full flex items-center justify-center">
|
|
46
|
+
<div className={cn(
|
|
47
|
+
larger ? 'h-[70%] w-[70%] ' : 'h-[75%] w-[75%]',
|
|
48
|
+
'rounded-full aspect-square bg-foreground' ,
|
|
49
|
+
)}/>
|
|
38
50
|
</RadioGroupPrimitive.Indicator>
|
|
39
51
|
</RadioGroupPrimitive.Item>
|
|
40
52
|
)
|
package/types/dimensions.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
// From Next Image Dim
|
|
2
1
|
type Dimensions = {
|
|
3
|
-
w: number
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
lg?: Dimensions
|
|
12
|
-
xl?: Dimensions
|
|
2
|
+
w: number
|
|
3
|
+
ar: number
|
|
4
|
+
} | {
|
|
5
|
+
h: number
|
|
6
|
+
ar: number
|
|
7
|
+
} | {
|
|
8
|
+
w: number
|
|
9
|
+
h: number
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
export {
|
|
16
|
-
type Dimensions
|
|
17
|
-
type TShirtDimensions
|
|
13
|
+
type Dimensions as default
|
|
18
14
|
}
|
|
19
|
-
|
|
20
|
-
|
package/types/image-def.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type Dimensions from './dimensions'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Defines a in image to render.
|
|
@@ -9,6 +9,8 @@ interface ImageDef {
|
|
|
9
9
|
src: string
|
|
10
10
|
/** defaults to short filename */
|
|
11
11
|
alt?: string
|
|
12
|
+
/** See next js docs */
|
|
13
|
+
sizes?: string
|
|
12
14
|
/**
|
|
13
15
|
* Tailwind class for svg files (usually a text-<color>).
|
|
14
16
|
* All affect 'fill' props in the svg file
|
|
@@ -21,7 +23,6 @@ interface ImageDef {
|
|
|
21
23
|
* can determine the aspect ratio
|
|
22
24
|
*/
|
|
23
25
|
dim: Dimensions
|
|
24
|
-
ar?: number
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export {
|
package/types/index.ts
CHANGED
|
@@ -8,7 +8,8 @@ export {
|
|
|
8
8
|
|
|
9
9
|
export { COMMON_GRID_1_COL, COMMON_GRID_2_COL, COMMON_GRID_3_COL, COMMON_GRID_4_COL } from './grid-def'
|
|
10
10
|
export type {default as GridDef, GridColumnSpec} from './grid-def'
|
|
11
|
-
export type {
|
|
11
|
+
export type { default as Dimensions } from './dimensions'
|
|
12
|
+
export type { default as TShirtDimensions } from './tshirt-dimensions'
|
|
12
13
|
export type { ContactInfo, ContactInfoFields } from './contact-info'
|
|
13
14
|
|
|
14
15
|
export type { default as BulletItem } from './bullet-item'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type ConcreteDimensions = {
|
|
2
|
+
w: number
|
|
3
|
+
h: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// TODO: This is only used by VideoDef.
|
|
7
|
+
// Is there a better way w @next/video?
|
|
8
|
+
type TShirtDimensions = {
|
|
9
|
+
xs?: ConcreteDimensions
|
|
10
|
+
sm?: ConcreteDimensions
|
|
11
|
+
md: ConcreteDimensions
|
|
12
|
+
lg?: ConcreteDimensions
|
|
13
|
+
xl?: ConcreteDimensions
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
type TShirtDimensions as default
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
package/types/video-def.ts
CHANGED
package/util/index.ts
CHANGED
|
@@ -50,16 +50,16 @@ export const asNum = (n: number | `${number}`): number => (
|
|
|
50
50
|
(typeof n === 'number') ? n : parseInt(n, 10)
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
+
export const resolveDimensions = (dim: Dimensions, constraint?: {w: number, h: number}): {w: number, h: number} => {
|
|
54
|
+
|
|
55
|
+
const resolved = ('w' in dim) ?
|
|
56
|
+
(('h' in dim) ? {w: dim.w, h: dim.h} : { w: dim.w, h: dim.w / dim.ar }) : { w: dim.h * dim.ar, h: dim.h }
|
|
57
|
+
|
|
58
|
+
return constraint ? constrain(resolved, constraint) : resolved
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
// https://stackoverflow.com/questions/3971841/how-to-resize-images-proportionally-keeping-the-aspect-ratio
|
|
54
|
-
export const constrain = (
|
|
55
|
-
const c = {
|
|
56
|
-
w: asNum(constraint.w),
|
|
57
|
-
h: asNum(constraint.h)
|
|
58
|
-
}
|
|
59
|
-
const d = {
|
|
60
|
-
w: asNum(dim.w),
|
|
61
|
-
h: asNum(dim.h)
|
|
62
|
-
}
|
|
62
|
+
export const constrain = (d: {w: number, h: number}, c: {w: number, h: number}): {w: number, h: number} => {
|
|
63
63
|
|
|
64
64
|
const ratio = Math.min(c.w / d.w, c.h / d.h)
|
|
65
65
|
return {
|