@luxfi/core 4.5.2 → 5.0.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/components/chat-widget.tsx +3 -2
- package/components/commerce/bag-button.tsx +41 -10
- package/components/commerce/buy-drawer/checkout.svg +4 -0
- package/components/commerce/buy-drawer/drawer.tsx +44 -0
- package/components/commerce/buy-drawer/index.tsx +192 -0
- package/components/commerce/checkout-button.tsx +116 -0
- package/components/commerce/checkout-panel/mb-checkout-panel.tsx +3 -4
- package/components/commerce/mobile-bag-drawer.tsx +1 -1
- package/components/header/desktop.tsx +1 -1
- package/components/index.ts +3 -0
- package/package.json +6 -3
- package/root-layout/index.tsx +9 -9
- package/style/cart-animation.css +29 -0
- package/style/checkout-animation.css +23 -0
- package/style/lux-global.css +1 -1
@@ -47,7 +47,7 @@ const ChatWidget: React.FC<{
|
|
47
47
|
return (<>
|
48
48
|
<div className={
|
49
49
|
'fixed bottom-0 sm:bottom-16 right-0 w-full h-full ' +
|
50
|
-
'sm:max-w-[400px] sm:max-h-[550px] sm:px-4 z-
|
50
|
+
'sm:max-w-[400px] sm:max-h-[550px] sm:px-4 z-floating ' +
|
51
51
|
(showChatbot ? 'flex' : 'hidden')
|
52
52
|
}>
|
53
53
|
<Card className='flex flex-col h-full w-full'>
|
@@ -66,7 +66,8 @@ const ChatWidget: React.FC<{
|
|
66
66
|
height={28}
|
67
67
|
onClick={onClick}
|
68
68
|
className={cn(
|
69
|
-
|
69
|
+
// z-index should be below anything in commerce-iu (buy drawer and checkout widget)
|
70
|
+
'fixed bottom-5 right-5 z-below-modal-3 transition-all cursor-pointer hover:drop-shadow-[0_2px_6px_rgba(255,255,255,1)]',
|
70
71
|
showChatbot ? 'rotate-180' : ''
|
71
72
|
)}
|
72
73
|
strokeWidth={1}
|
@@ -1,23 +1,26 @@
|
|
1
1
|
'use client'
|
2
|
-
import React from 'react'
|
2
|
+
import React, { useEffect, useRef } from 'react'
|
3
|
+
import { observable, type IObservableValue, reaction } from 'mobx'
|
3
4
|
import { observer } from 'mobx-react-lite'
|
4
5
|
|
5
|
-
import { buttonVariants
|
6
|
-
import { cn } from '@hanzo/ui/util'
|
6
|
+
import { buttonVariants } from '@hanzo/ui/primitives'
|
7
|
+
import { cn, type VariantProps } from '@hanzo/ui/util'
|
7
8
|
import { useCommerce } from '@hanzo/commerce'
|
8
9
|
|
9
10
|
import * as Icons from '../icons'
|
10
11
|
|
11
12
|
const BagButton: React.FC<{
|
12
13
|
showIfEmpty?: boolean
|
13
|
-
|
14
|
-
|
14
|
+
animateOnHover?: boolean
|
15
|
+
animateOnQuantityChange?: boolean
|
16
|
+
size?: VariantProps<typeof buttonVariants>['size']
|
15
17
|
className?: string
|
16
18
|
iconClx?: string
|
17
19
|
onClick?: () => void
|
18
20
|
}> = observer(({
|
19
21
|
showIfEmpty=false,
|
20
|
-
|
22
|
+
animateOnHover=true,
|
23
|
+
animateOnQuantityChange=true,
|
21
24
|
size='default',
|
22
25
|
className='',
|
23
26
|
iconClx='',
|
@@ -25,12 +28,34 @@ const BagButton: React.FC<{
|
|
25
28
|
}) => {
|
26
29
|
|
27
30
|
const c = useCommerce()
|
31
|
+
const wiggleRef = useRef<IObservableValue<'more' | 'less' | 'none'>>(observable.box('none'))
|
32
|
+
|
33
|
+
useEffect(() => (
|
34
|
+
// return IReactionDisposer
|
35
|
+
animateOnQuantityChange ? reaction(
|
36
|
+
() => (c.cartQuantity),
|
37
|
+
(curr, prev) => {
|
38
|
+
if (curr > prev) {
|
39
|
+
wiggleRef.current.set('more')
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
wiggleRef.current.set('less')
|
43
|
+
}
|
44
|
+
setTimeout(() => {
|
45
|
+
// Note that this doesn't actually stop the animation
|
46
|
+
// just resets the styles
|
47
|
+
wiggleRef.current.set('none')
|
48
|
+
}, 800)
|
49
|
+
}
|
50
|
+
) : undefined
|
51
|
+
), [])
|
28
52
|
|
29
53
|
// undefined means context is not installed, ie commerce functions are not in use
|
30
54
|
if (!c || (!showIfEmpty && c.cartEmpty)) {
|
31
55
|
return <div /> // trigger code needs non-null
|
32
56
|
}
|
33
57
|
|
58
|
+
|
34
59
|
return (
|
35
60
|
<div
|
36
61
|
aria-label="Bag"
|
@@ -40,6 +65,10 @@ const BagButton: React.FC<{
|
|
40
65
|
buttonVariants({ variant: 'ghost', size, rounded: 'md' }),
|
41
66
|
// Overides the bg change on hover --not a "hover effect"
|
42
67
|
'relative group p-0 aspect-square hover:bg-background',
|
68
|
+
((wiggleRef.current.get() === 'more') ?
|
69
|
+
'item-added-to-cart-animation'
|
70
|
+
:
|
71
|
+
(wiggleRef.current.get() === 'less') ? 'item-removed-from-cart-animation' : ''),
|
43
72
|
className
|
44
73
|
)}
|
45
74
|
>
|
@@ -53,12 +82,14 @@ const BagButton: React.FC<{
|
|
53
82
|
<div>{c.cartQuantity}</div>
|
54
83
|
</div>
|
55
84
|
)}
|
56
|
-
<Icons.bag className={cn(
|
57
|
-
'relative -top-[3px] fill-primary
|
85
|
+
<Icons.bag width='24' height='28' className={cn(
|
86
|
+
'relative -top-[3px] fill-primary',
|
58
87
|
iconClx,
|
59
|
-
(
|
88
|
+
(animateOnHover ?
|
60
89
|
'group-hover:fill-primary-hover group-hover:scale-105 transition-scale transition-duration-300'
|
61
|
-
|
90
|
+
:
|
91
|
+
''
|
92
|
+
)
|
62
93
|
)} aria-hidden="true" />
|
63
94
|
</div>
|
64
95
|
)
|
@@ -0,0 +1,4 @@
|
|
1
|
+
<svg viewBox="0 0 32 40" >
|
2
|
+
<path d="M27.724,27.85a3.4,3.4,0,0,0,.825-2.672l-1.8-14.4A3.405,3.405,0,0,0,23.375,7.8h-.521a5.173,5.173,0,0,0-10.32,0H3.894a1,1,0,0,0,0,2H8.929a3.316,3.316,0,0,0-.29.978l-.382,3.055a.889.889,0,0,0-.163-.033h-3a1,1,0,0,0,0,2H8.011L6.839,25.178A3.4,3.4,0,0,0,10.212,29H25.175A3.4,3.4,0,0,0,27.724,27.85ZM17.694,5a3.2,3.2,0,0,1,3.16,2.8h-6.32A3.194,3.194,0,0,1,17.694,5ZM9.163,26.526a1.384,1.384,0,0,1-.34-1.1l1.8-14.4A1.4,1.4,0,0,1,12.013,9.8h.481V13a1,1,0,0,0,2,0V9.8h6.4V13a1,1,0,0,0,2,0V9.8h.481a1.4,1.4,0,0,1,1.39,1.226l1.8,14.4A1.4,1.4,0,0,1,25.175,27H10.212A1.38,1.38,0,0,1,9.163,26.526Z"/>
|
3
|
+
<path d="M7.894,11.8a1,1,0,0,0-1-1h-3a1,1,0,0,0,0,2h3A1,1,0,0,0,7.894,11.8Z"/>
|
4
|
+
</svg>
|
@@ -0,0 +1,44 @@
|
|
1
|
+
'use client'
|
2
|
+
import React, {type PropsWithChildren } from 'react'
|
3
|
+
|
4
|
+
import { X as LucideX} from 'lucide-react'
|
5
|
+
|
6
|
+
import { Button, Drawer, DrawerContent, type DrawerProps } from '@hanzo/ui/primitives'
|
7
|
+
import { cn } from '@hanzo/ui/util'
|
8
|
+
|
9
|
+
const CommerceDrawer: React.FC<PropsWithChildren &
|
10
|
+
Omit<DrawerProps, 'onOpenChange'> &
|
11
|
+
{
|
12
|
+
setOpen: (b: boolean) => void
|
13
|
+
drawerClx?: string
|
14
|
+
}
|
15
|
+
> = ({
|
16
|
+
children,
|
17
|
+
open,
|
18
|
+
setOpen,
|
19
|
+
modal,
|
20
|
+
drawerClx='',
|
21
|
+
...rest
|
22
|
+
}) => (
|
23
|
+
// @ts-ignore
|
24
|
+
<Drawer open={open} onOpenChange={setOpen} modal={modal} {...rest}>
|
25
|
+
<DrawerContent modal={modal} className={cn(
|
26
|
+
'rounded-t-xl mt-6 pt-6',
|
27
|
+
drawerClx
|
28
|
+
)}>
|
29
|
+
{children}
|
30
|
+
<Button
|
31
|
+
variant='ghost'
|
32
|
+
size='icon'
|
33
|
+
onClick={() => {setOpen(false)}}
|
34
|
+
className={'absolute top-4 right-4 w-8 h-8 group rounded-full p-1 hidden md:flex items-center'}
|
35
|
+
>
|
36
|
+
<LucideX className='w-6 h-6 text-muted group-hover:text-foreground'/>
|
37
|
+
</Button>
|
38
|
+
</DrawerContent>
|
39
|
+
</Drawer>
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
export default CommerceDrawer
|
44
|
+
|
@@ -0,0 +1,192 @@
|
|
1
|
+
'use client'
|
2
|
+
import React, { useEffect, useRef } from 'react'
|
3
|
+
import { createPortal } from 'react-dom'
|
4
|
+
import { useRouter, usePathname } from 'next/navigation'
|
5
|
+
import { ObservableSet, observable, reaction, type IReactionDisposer, runInAction} from 'mobx'
|
6
|
+
import { observer } from 'mobx-react-lite'
|
7
|
+
|
8
|
+
import { cn } from '@hanzo/ui/util'
|
9
|
+
import { Image } from '@hanzo/ui/primitives'
|
10
|
+
|
11
|
+
import { useCommerce, useCommerceUI, CarouselBuyCard, LineItemRef } from '@hanzo/commerce'
|
12
|
+
import type { LineItem } from '@hanzo/commerce/types'
|
13
|
+
|
14
|
+
import CommerceDrawer from './drawer'
|
15
|
+
import CheckoutButton from '../checkout-button'
|
16
|
+
|
17
|
+
const DEF_IMG_CONSTRAINT={w: 40, h: 24}
|
18
|
+
const CO_ANIM_DURATION = 400
|
19
|
+
const CO_ANIM_TIMING_FN = 'cubic-bezier(0.4, 0, 0.2, 1)'
|
20
|
+
const CO_WIDGET_W_CLX = {
|
21
|
+
checkout: 'w-pr-40',
|
22
|
+
itemInfo: 'w-pr-60'
|
23
|
+
}
|
24
|
+
|
25
|
+
const CO_WIDGET_SHADOW_STYLE = {
|
26
|
+
border: '1px solid rgb(100 100 100)',
|
27
|
+
boxShadow: '2px 4px 4px -3px rgb(125 125 125 / 0.7), 4px -4px 8px -4px rgb(125 125 125 / 0.7)'
|
28
|
+
}
|
29
|
+
|
30
|
+
const CommerceUIComponent: React.FC = observer(() => {
|
31
|
+
|
32
|
+
const ui = useCommerceUI()
|
33
|
+
const cmmc = useCommerce()
|
34
|
+
const router = useRouter()
|
35
|
+
const isCheckout = usePathname() === '/checkout'
|
36
|
+
|
37
|
+
const animClxRef = useRef<ObservableSet<string>>(observable.set(new Set<string>(
|
38
|
+
(cmmc.cartEmpty || ui.buyOptionsSkuPath || isCheckout) ? ['hidden'] : []
|
39
|
+
)))
|
40
|
+
|
41
|
+
// a ref that is synced to ui.activeItem, but persists for CO_ANIM_DURATION longer
|
42
|
+
// so ui does not jump while animating "out"
|
43
|
+
const laggingActiveItemRef = useRef<LineItemRef>(new LineItemRef())
|
44
|
+
const disposersRef = useRef<IReactionDisposer[]>([])
|
45
|
+
|
46
|
+
useEffect(() => {
|
47
|
+
disposersRef.current.push(
|
48
|
+
reaction(
|
49
|
+
() => (ui.activeItem),
|
50
|
+
(item: LineItem | undefined) => {
|
51
|
+
if (item) {
|
52
|
+
laggingActiveItemRef.current.set(item)
|
53
|
+
}
|
54
|
+
else {
|
55
|
+
setTimeout(() => { laggingActiveItemRef.current.set(undefined) }, CO_ANIM_DURATION)
|
56
|
+
}
|
57
|
+
},
|
58
|
+
{equals: (val, prev) => (val?.sku === prev?.sku)}
|
59
|
+
)
|
60
|
+
)
|
61
|
+
|
62
|
+
disposersRef.current.push(
|
63
|
+
reaction(
|
64
|
+
() => ({
|
65
|
+
microOpen: !(cmmc.cartEmpty || !!ui.buyOptionsSkuPath || isCheckout),
|
66
|
+
buyOpen: !!ui.buyOptionsSkuPath
|
67
|
+
}),
|
68
|
+
(val, prev) => {
|
69
|
+
|
70
|
+
runInAction(() => {
|
71
|
+
if (!val.microOpen && prev.microOpen) {
|
72
|
+
animClxRef.current.add('checkout-widget-disappears')
|
73
|
+
}
|
74
|
+
else if (val.microOpen && !prev.microOpen) {
|
75
|
+
animClxRef.current.delete('hidden')
|
76
|
+
animClxRef.current.add('checkout-widget-appears')
|
77
|
+
}
|
78
|
+
if (!val.buyOpen && prev.buyOpen) {
|
79
|
+
animClxRef.current.add('checkout-widget-appears-after-buy-drawer-closes')
|
80
|
+
}
|
81
|
+
else {
|
82
|
+
animClxRef.current.delete('checkout-widget-appears-after-buy-drawer-closes')
|
83
|
+
}
|
84
|
+
})
|
85
|
+
|
86
|
+
setTimeout(() => {runInAction(() => {
|
87
|
+
animClxRef.current.delete('checkout-widget-appears')
|
88
|
+
animClxRef.current.delete('checkout-widget-appears-after-buy-drawer-closes')
|
89
|
+
if (animClxRef.current.has('checkout-widget-disappears') ) {
|
90
|
+
animClxRef.current.delete('checkout-widget-disappears')
|
91
|
+
animClxRef.current.add('hidden')
|
92
|
+
}
|
93
|
+
})}, 800)
|
94
|
+
},
|
95
|
+
{equals: (val, prev) => (
|
96
|
+
val.microOpen === prev.microOpen
|
97
|
+
&&
|
98
|
+
val.buyOpen === prev.buyOpen
|
99
|
+
)}
|
100
|
+
)
|
101
|
+
)
|
102
|
+
|
103
|
+
return () => {
|
104
|
+
disposersRef.current.forEach((d) => {d()})
|
105
|
+
}
|
106
|
+
}, [])
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
const handleCheckout = () => {
|
111
|
+
// router.push('/checkout')
|
112
|
+
}
|
113
|
+
|
114
|
+
// Should only ever be called internally to close
|
115
|
+
const reallyOnlyCloseDrawer = (b: boolean) => {
|
116
|
+
if (!b ) {
|
117
|
+
ui.hideBuyOptions()
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
return (<>
|
122
|
+
<CommerceDrawer
|
123
|
+
open={!!ui.buyOptionsSkuPath}
|
124
|
+
setOpen={reallyOnlyCloseDrawer}
|
125
|
+
modal={true}
|
126
|
+
drawerClx={'w-full md:max-w-[550px] md:mx-auto lg:max-w-[50vw]'}
|
127
|
+
>
|
128
|
+
<CarouselBuyCard
|
129
|
+
skuPath={ui.buyOptionsSkuPath!}
|
130
|
+
checkoutButton={
|
131
|
+
<CheckoutButton handleCheckout={handleCheckout} className='w-full min-w-[160px] sm:max-w-[320px]'/>
|
132
|
+
}
|
133
|
+
clx='w-full'
|
134
|
+
addBtnClx='w-full min-w-[160px] sm:max-w-[320px]'
|
135
|
+
selectorClx='max-w-[475px]'
|
136
|
+
/>
|
137
|
+
</CommerceDrawer>
|
138
|
+
{globalThis?.document?.body && createPortal(
|
139
|
+
<div
|
140
|
+
className={cn(
|
141
|
+
'min-w-[160px] sm:max-w-[320px] w-[calc(100%-32px)] mx-auto !h-10',
|
142
|
+
'z-below-modal-2 fixed bottom-[20px] left-0 right-0',
|
143
|
+
'rounded-lg bg-background',
|
144
|
+
'flex',
|
145
|
+
ui.activeItem ? 'gap-2' : '',
|
146
|
+
Array.from(animClxRef.current).join(' ')
|
147
|
+
)}
|
148
|
+
style={laggingActiveItemRef.current.item ? {} : CO_WIDGET_SHADOW_STYLE}
|
149
|
+
>
|
150
|
+
<div
|
151
|
+
className={cn(
|
152
|
+
'flex flex-row justify-between items-center',
|
153
|
+
ui.activeItem ? CO_WIDGET_W_CLX.itemInfo : 'w-0',
|
154
|
+
laggingActiveItemRef.current.item ? 'px-3 border rounded-lg border-muted-3' : ''
|
155
|
+
)}
|
156
|
+
style={{
|
157
|
+
transitionProperty: 'width',
|
158
|
+
transitionTimingFunction: CO_ANIM_TIMING_FN,
|
159
|
+
transitionDuration: `${CO_ANIM_DURATION}ms`
|
160
|
+
}}
|
161
|
+
>
|
162
|
+
{laggingActiveItemRef.current.item?.img ? (
|
163
|
+
<Image def={laggingActiveItemRef.current.item.img} constrainTo={DEF_IMG_CONSTRAINT} preload className='grow-0 shrink-0'/>
|
164
|
+
) : ( // placeholder so things align
|
165
|
+
<div style={{height: DEF_IMG_CONSTRAINT.h, width: DEF_IMG_CONSTRAINT.w}} className='bg-level-3 grow-0 shrink-0'/>
|
166
|
+
)}
|
167
|
+
|
168
|
+
<div className='text-muted grow ml-1'>
|
169
|
+
{laggingActiveItemRef.current.item && (<>
|
170
|
+
<p className='whitespace-nowrap text-sm'>{laggingActiveItemRef.current.item.title}</p>
|
171
|
+
<p className='whitespace-nowrap text-xxs' >recently added...</p>
|
172
|
+
</>)}
|
173
|
+
</div>
|
174
|
+
</div>
|
175
|
+
<CheckoutButton
|
176
|
+
handleCheckout={handleCheckout}
|
177
|
+
centerText={!!!ui.activeItem}
|
178
|
+
variant='primary' rounded='lg'
|
179
|
+
className={cn(ui.activeItem ? CO_WIDGET_W_CLX.checkout : 'w-full')}
|
180
|
+
style={{
|
181
|
+
transitionProperty: 'width',
|
182
|
+
transitionTimingFunction: CO_ANIM_TIMING_FN,
|
183
|
+
transitionDuration: `${CO_ANIM_DURATION}ms`
|
184
|
+
}}
|
185
|
+
/>
|
186
|
+
</div>,
|
187
|
+
globalThis?.document?.body
|
188
|
+
)}
|
189
|
+
</>)
|
190
|
+
})
|
191
|
+
|
192
|
+
export default CommerceUIComponent
|
@@ -0,0 +1,116 @@
|
|
1
|
+
'use client'
|
2
|
+
import React, { useEffect, useRef } from 'react'
|
3
|
+
import { observable, type IObservableValue, reaction } from 'mobx'
|
4
|
+
import { observer } from 'mobx-react-lite'
|
5
|
+
import { type LucideProps } from 'lucide-react'
|
6
|
+
|
7
|
+
import { Button, type ButtonProps } from '@hanzo/ui/primitives'
|
8
|
+
import { cn } from '@hanzo/ui/util'
|
9
|
+
import { useCommerce } from '@hanzo/commerce'
|
10
|
+
|
11
|
+
import * as Icons from '../icons'
|
12
|
+
|
13
|
+
const IconAndQuantity: React.FC<{
|
14
|
+
animateOnQuantityChange?: boolean
|
15
|
+
clx?: string
|
16
|
+
iconClx?: string
|
17
|
+
digitClx?: string
|
18
|
+
}> = observer(({
|
19
|
+
animateOnQuantityChange=true,
|
20
|
+
clx='',
|
21
|
+
iconClx='',
|
22
|
+
digitClx=''
|
23
|
+
}) => {
|
24
|
+
|
25
|
+
const cmmc = useCommerce()
|
26
|
+
const wiggleRef = useRef<IObservableValue<'more' | 'less' | 'none'>>(observable.box('none'))
|
27
|
+
|
28
|
+
useEffect(() => (
|
29
|
+
// return IReactionDisposer
|
30
|
+
animateOnQuantityChange ? reaction(
|
31
|
+
() => (cmmc.cartQuantity),
|
32
|
+
(curr, prev) => {
|
33
|
+
if (curr > prev) {
|
34
|
+
wiggleRef.current.set('more')
|
35
|
+
}
|
36
|
+
else {
|
37
|
+
wiggleRef.current.set('less')
|
38
|
+
}
|
39
|
+
setTimeout(() => {
|
40
|
+
// Note that this doesn't actually stop the animation
|
41
|
+
// just resets the styles
|
42
|
+
wiggleRef.current.set('none')
|
43
|
+
}, 800)
|
44
|
+
}
|
45
|
+
) : undefined
|
46
|
+
), [])
|
47
|
+
|
48
|
+
return (
|
49
|
+
<div className={cn('flex items-center justify-center', clx)}>
|
50
|
+
<div className={cn(
|
51
|
+
'relative flex items-center justify-center mr-1',
|
52
|
+
((wiggleRef.current.get() === 'more') ?
|
53
|
+
'item-added-to-cart-animation'
|
54
|
+
:
|
55
|
+
(wiggleRef.current.get() === 'less') ? 'item-removed-from-cart-animation' : ''),
|
56
|
+
)} >
|
57
|
+
{cmmc.cartQuantity > 0 && (
|
58
|
+
<div className={cn(
|
59
|
+
'z-above-content flex flex-col justify-center items-center',
|
60
|
+
'absolute left-0 right-0 top-0 bottom-0',
|
61
|
+
digitClx
|
62
|
+
)}>
|
63
|
+
<div style={{color: 'white' /* tailwind bug? */, fontSize: '11px', position: 'relative', top: '1px' }}>{cmmc.cartQuantity}</div>
|
64
|
+
</div>
|
65
|
+
)}
|
66
|
+
<Icons.bag width='19' height='24' className={cn('relative -top-[3px] opacity-70' , iconClx)} aria-hidden="true" />
|
67
|
+
</div>
|
68
|
+
<span style={{fontSize: '17px',}}>›</span>
|
69
|
+
</div>
|
70
|
+
)
|
71
|
+
})
|
72
|
+
|
73
|
+
const CheckoutButton: React.FC<ButtonProps & {
|
74
|
+
handleCheckout: () => void
|
75
|
+
showQuantity?: boolean
|
76
|
+
animateOnQuantityChange?: boolean
|
77
|
+
centerText?: boolean
|
78
|
+
}> = ({
|
79
|
+
handleCheckout,
|
80
|
+
variant='primary',
|
81
|
+
rounded='lg',
|
82
|
+
className,
|
83
|
+
showQuantity=true,
|
84
|
+
animateOnQuantityChange=true,
|
85
|
+
centerText=true,
|
86
|
+
...rest
|
87
|
+
}) => {
|
88
|
+
|
89
|
+
return (
|
90
|
+
<Button
|
91
|
+
{...rest}
|
92
|
+
onClick={handleCheckout}
|
93
|
+
variant={variant}
|
94
|
+
rounded={rounded}
|
95
|
+
className={cn(
|
96
|
+
className,
|
97
|
+
'flex justify-between items-stretch',
|
98
|
+
showQuantity ? (centerText ? 'px-1.5' : 'pl-2.5 pr-1.5') : ''
|
99
|
+
)}
|
100
|
+
>
|
101
|
+
{showQuantity && centerText && (
|
102
|
+
<IconAndQuantity clx='invisible' />
|
103
|
+
)}
|
104
|
+
<div className='flex justify-center items-center'>Checkout</div>
|
105
|
+
{showQuantity && (
|
106
|
+
<IconAndQuantity
|
107
|
+
animateOnQuantityChange={animateOnQuantityChange}
|
108
|
+
iconClx='fill-fg-foreground'
|
109
|
+
digitClx='text-primary-fg leading-none font-bold font-sans'
|
110
|
+
/>
|
111
|
+
)}
|
112
|
+
</Button>
|
113
|
+
)
|
114
|
+
}
|
115
|
+
|
116
|
+
export default CheckoutButton
|
@@ -32,12 +32,11 @@ const MobileCheckoutPanel: React.FC<PropsWithChildren & {
|
|
32
32
|
<CartAccordian
|
33
33
|
icon={
|
34
34
|
<BagButton
|
35
|
-
|
35
|
+
animateOnHover={false}
|
36
36
|
showIfEmpty
|
37
37
|
size='sm'
|
38
|
-
className=
|
39
|
-
'
|
40
|
-
iconClx='fill-foreground '
|
38
|
+
className='mr-1 relative w-5 h-6 sm:w-6 sm:h-7'
|
39
|
+
iconClx='fill-foreground'
|
41
40
|
/>
|
42
41
|
}
|
43
42
|
className='flex items-center justify-center w-full'
|
@@ -25,7 +25,7 @@ const DesktopHeader: React.FC<{
|
|
25
25
|
// TODO move 13px into a size class and configure twMerge to recognize say, 'text-size-nav'
|
26
26
|
// (vs be beat out by 'text-color-nav')
|
27
27
|
return (
|
28
|
-
<header className={cn('bg-background
|
28
|
+
<header className={cn('bg-background fixed z-header top-0 left-0 right-0', className)} >
|
29
29
|
{/* md or larger */}
|
30
30
|
<div className={
|
31
31
|
'flex flex-row h-[80px] items-center justify-between ' +
|
package/components/index.ts
CHANGED
@@ -10,7 +10,10 @@ export { default as MiniChart } from './mini-chart'
|
|
10
10
|
export { default as NotFound } from './not-found'
|
11
11
|
|
12
12
|
|
13
|
+
|
14
|
+
export { default as BuyDrawer } from './commerce/buy-drawer'
|
13
15
|
export { default as CheckoutPanel } from './commerce/checkout-panel'
|
16
|
+
export { default as CheckoutButton } from './commerce/checkout-button'
|
14
17
|
export { default as LoginPanel } from './auth/login-panel'
|
15
18
|
export { default as AuthListener } from './auth/auth-listener'
|
16
19
|
export { default as Scripts } from './scripts'
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@luxfi/core",
|
3
|
-
"version": "
|
3
|
+
"version": "5.0.0",
|
4
4
|
"description": "Library that contains shared UI primitives, support for a common design system, and other boilerplate support.",
|
5
5
|
"publishConfig": {
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
@@ -37,8 +37,8 @@
|
|
37
37
|
},
|
38
38
|
"dependencies": {
|
39
39
|
"@hanzo/auth": "2.4.6",
|
40
|
-
"@hanzo/commerce": "
|
41
|
-
"@hanzo/ui": "3.
|
40
|
+
"@hanzo/commerce": "7.0.0",
|
41
|
+
"@hanzo/ui": "3.7.0",
|
42
42
|
"@next/third-parties": "^14.1.0",
|
43
43
|
"cookies-next": "^4.1.1",
|
44
44
|
"date-fns": "^3.6.0",
|
@@ -47,6 +47,9 @@
|
|
47
47
|
"react-social-icons": "^6.4.0"
|
48
48
|
},
|
49
49
|
"peerDependencies": {
|
50
|
+
"@hanzo/auth": "2.4.6",
|
51
|
+
"@hanzo/commerce": "6.4.7",
|
52
|
+
"@hanzo/ui": "3.6.5",
|
50
53
|
"@hookform/resolvers": "^3.3.2",
|
51
54
|
"lucide-react": "^0.344.0",
|
52
55
|
"next": "14.1.3",
|
package/root-layout/index.tsx
CHANGED
@@ -53,16 +53,16 @@ const RootLayout: React.FC<PropsWithChildren & {
|
|
53
53
|
}) => {
|
54
54
|
|
55
55
|
const currentUser = await getUserServerSide()
|
56
|
-
const usingCommerce = siteDef
|
56
|
+
const usingCommerce = siteDef?.ext?.commerce && siteDef.ext.commerce.rootNode && siteDef.ext.commerce.families
|
57
57
|
|
58
58
|
const Guts: React.FC = () => (<>
|
59
59
|
{showHeader && <Header siteDef={siteDef}/>}
|
60
60
|
{children}
|
61
|
-
{chatbot && (
|
62
|
-
<ChatWidget
|
63
|
-
title='LUX'
|
64
|
-
subtitle='AI'
|
65
|
-
chatbotUrl='https://lux.chat/iframe'
|
61
|
+
{chatbot && (
|
62
|
+
<ChatWidget
|
63
|
+
title='LUX'
|
64
|
+
subtitle='AI'
|
65
|
+
chatbotUrl='https://lux.chat/iframe'
|
66
66
|
suggestedQuestions={siteDef.ext?.chatBot?.suggestedQuestions ?? []}
|
67
67
|
/>
|
68
68
|
)}
|
@@ -88,15 +88,15 @@ const RootLayout: React.FC<PropsWithChildren & {
|
|
88
88
|
<Scripts/>
|
89
89
|
<AuthServiceProvider user={currentUser} conf={{} as AuthServiceConf}>
|
90
90
|
{usingCommerce ? (
|
91
|
-
<CommerceProvider
|
92
|
-
rootNode={siteDef.ext.commerce.rootNode}
|
91
|
+
<CommerceProvider
|
92
|
+
rootNode={siteDef.ext.commerce.rootNode}
|
93
93
|
families={siteDef.ext.commerce.families}
|
94
94
|
options={siteDef.ext.commerce.options}
|
95
95
|
uiSpecs={selectionUISpecifiers}
|
96
96
|
>
|
97
97
|
<Guts />
|
98
98
|
</CommerceProvider>
|
99
|
-
) : (
|
99
|
+
) : (
|
100
100
|
<Guts />
|
101
101
|
)}
|
102
102
|
<AuthListener/>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
@keyframes wiggle-larger {
|
2
|
+
0% { transform: rotate(0deg); }
|
3
|
+
18% { transform: rotate(10deg) scale(0.9); }
|
4
|
+
32% { transform: rotate(0deg) scale(1.05);; }
|
5
|
+
50% { transform: rotate(-10deg) scale(1.05); }
|
6
|
+
68% { transform: rotate(0deg) scale(1.1); }
|
7
|
+
84% { transform: rotate(10deg) scale(1); }
|
8
|
+
100% { transform: rotate(0deg) scale(1);}
|
9
|
+
}
|
10
|
+
|
11
|
+
@keyframes wiggle-smaller {
|
12
|
+
0% { transform: rotate(0deg); }
|
13
|
+
18% { transform: rotate(10deg) scale(1.05); }
|
14
|
+
32% { transform: rotate(0deg) scale(0.9);; }
|
15
|
+
50% { transform: rotate(-10deg) scale(0.9); }
|
16
|
+
68% { transform: rotate(0deg) scale(0.85); }
|
17
|
+
84% { transform: rotate(10deg) scale(1); }
|
18
|
+
100% { transform: rotate(0deg) scale(1);}
|
19
|
+
}
|
20
|
+
|
21
|
+
.wiggle-larger-animation,
|
22
|
+
.item-added-to-cart-animation {
|
23
|
+
animation: wiggle-larger 350ms;
|
24
|
+
}
|
25
|
+
|
26
|
+
.wiggle-smaller-animation,
|
27
|
+
.item-removed-from-cart-animation {
|
28
|
+
animation: wiggle-smaller 300ms;
|
29
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
@keyframes checkout-widget-fade-in {
|
2
|
+
0% { transform: scale(0.5); opacity: 0; }
|
3
|
+
100% { transform: scale(1); opacity: 1;}
|
4
|
+
}
|
5
|
+
|
6
|
+
.checkout-widget-appears {
|
7
|
+
animation-name: checkout-widget-fade-in;
|
8
|
+
animation-duration: 200ms;
|
9
|
+
}
|
10
|
+
|
11
|
+
.checkout-widget-disappears {
|
12
|
+
animation-name: checkout-widget-fade-in;
|
13
|
+
animation-duration: 200ms;
|
14
|
+
animation-direction: reverse;
|
15
|
+
animation-fill-mode: forwards;
|
16
|
+
}
|
17
|
+
|
18
|
+
.checkout-widget-appears-after-buy-drawer-closes {
|
19
|
+
animation-fill-mode: backwards;
|
20
|
+
/* Drawer is hardcoded to open in 0.5s. We can start slightly sooner. */
|
21
|
+
animation-delay: 400ms;
|
22
|
+
}
|
23
|
+
|