@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.
@@ -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-above-floating ' +
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
- 'fixed bottom-5 right-5 z-floating transition-all cursor-pointer hover:drop-shadow-[0_2px_6px_rgba(255,255,255,1)]',
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, type ButtonSizes } from '@hanzo/ui/primitives'
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
- noHoverEffects?: boolean
14
- size?: ButtonSizes
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
- noHoverEffects=false,
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 w-6 h-7 ',
85
+ <Icons.bag width='24' height='28' className={cn(
86
+ 'relative -top-[3px] fill-primary',
58
87
  iconClx,
59
- (noHoverEffects ? '' : (
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',}}>&rsaquo;</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
- noHoverEffects
35
+ animateOnHover={false}
36
36
  showIfEmpty
37
37
  size='sm'
38
- className=
39
- 'mr-1 relative w-5 h-6 sm:w-6 sm:h-7 '
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'
@@ -33,7 +33,7 @@ const MobileBagDrawer: React.FC<{
33
33
  >
34
34
  <div className='flex flex-row items-center flex-none justify-center '>
35
35
  <BagButton
36
- noHoverEffects
36
+ animateOnHover={false}
37
37
  showIfEmpty
38
38
  className=
39
39
  'mr-2 relative w-6 h-7'
@@ -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 sticky z-header top-0 ', className)} >
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 ' +
@@ -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": "4.5.2",
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": "6.4.7",
41
- "@hanzo/ui": "3.6.5",
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",
@@ -53,16 +53,16 @@ const RootLayout: React.FC<PropsWithChildren & {
53
53
  }) => {
54
54
 
55
55
  const currentUser = await getUserServerSide()
56
- const usingCommerce = siteDef.ext.commerce && siteDef.ext.commerce.rootNode && siteDef.ext.commerce.families
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
+
@@ -10,7 +10,7 @@
10
10
  @import "tailwindcss/components";
11
11
  @import "tailwindcss/utilities";
12
12
 
13
- .nextjs-toast-errors-parent {
13
+ div.nextjs-toast-errors-parent[data-nextjs-toast="true"] {
14
14
  display: none;
15
15
  }
16
16