@luxfi/core 5.0.9 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,5 +13,6 @@ export const getBullionFamilies = (videoMap: Map<string, VideoDef>) => (
13
13
  )
14
14
  )
15
15
 
16
- export { default as serviceOptions } from '../../conf/lux-commerce-options'
16
+ export { default as serviceOptions } from './lux-service-options'
17
17
  export { default as bullionPrice1oz } from './bullion-price-1oz'
18
+
@@ -3,70 +3,29 @@ import React, {type PropsWithChildren } from 'react'
3
3
 
4
4
  import { X as LucideX} from 'lucide-react'
5
5
 
6
- import {
7
- Button,
8
- Drawer,
9
- DrawerContent,
10
- DrawerHandle,
11
- type DrawerProps,
12
- useDrawerContext
13
- } from '@hanzo/ui/primitives'
6
+ import { Button, Drawer, DrawerContent, type DrawerProps } from '@hanzo/ui/primitives'
14
7
  import { cn } from '@hanzo/ui/util'
15
8
 
16
- import '../../../style/drawer-handle-overrides.css'
17
-
18
9
  const CommerceDrawer: React.FC<PropsWithChildren &
19
10
  Omit<DrawerProps, 'onOpenChange'> &
20
11
  {
21
12
  setOpen: (b: boolean) => void
22
- handleHandleClicked: () => void
23
13
  drawerClx?: string
24
- setActiveSPIndexSetter?: (fn: (snapPoint: number | string | null) => void) => void
25
14
  }
26
15
  > = ({
27
16
  children,
28
17
  open,
29
18
  setOpen,
30
19
  modal,
31
- snapPoints,
32
- setActiveSnapPoint,
33
- activeSnapPoint,
34
- handleHandleClicked,
35
- setActiveSPIndexSetter,
36
20
  drawerClx='',
37
21
  ...rest
38
- }) => {
39
-
40
-
41
- return (
22
+ }) => (
42
23
  // @ts-ignore
43
- <Drawer
44
- open={open}
45
- onOpenChange={setOpen}
46
- modal={modal}
47
- snapPoints={snapPoints}
48
- setActiveSnapPoint={setActiveSnapPoint}
49
- activeSnapPoint={activeSnapPoint}
50
- fastDragSkipsToEnd={false}
51
- handleOnly={true}
52
- setActiveSPIndexSetter={setActiveSPIndexSetter}
53
-
54
-
55
- {...rest}
56
- >
57
- <DrawerContent defaultHandle={false} className={cn(
24
+ <Drawer open={open} onOpenChange={setOpen} modal={modal} {...rest}>
25
+ <DrawerContent modal={modal} className={cn(
58
26
  'rounded-t-xl mt-6 pt-6',
59
27
  drawerClx
60
28
  )}>
61
-
62
- <DrawerHandle
63
- className={
64
- 'absolute left-0 right-0 mx-auto top-2 ' +
65
- 'w-[100px] h-3 rounded-full bg-level-3 hover:bg-level-2 shrink-0'
66
- }
67
- handleClick={handleHandleClicked}
68
- />
69
-
70
29
  {children}
71
30
  <Button
72
31
  variant='ghost'
@@ -79,7 +38,6 @@ const CommerceDrawer: React.FC<PropsWithChildren &
79
38
  </DrawerContent>
80
39
  </Drawer>
81
40
  )
82
- }
83
41
 
84
42
 
85
43
  export default CommerceDrawer
@@ -1,242 +1,44 @@
1
1
  'use client'
2
- import React, { useEffect, useRef, useState } from 'react'
3
- import { usePathname, useRouter } from 'next/navigation'
4
- import { action, computed, makeObservable, observable, reaction, type IReactionDisposer } from 'mobx'
2
+ import React from 'react'
3
+ import { useRouter } from 'next/navigation'
5
4
  import { observer } from 'mobx-react-lite'
6
5
 
7
- import { CarouselBuyCard, useCommerce } from '@hanzo/commerce'
8
-
9
- import { useCommerceUI } from '../../../commerce/ui-context'
6
+ import { useCommerceUI, CarouselBuyCard } from '@hanzo/commerce'
10
7
 
11
8
  import CommerceDrawer from './drawer'
12
9
  import CheckoutButton from '../checkout-button'
13
10
 
14
- const BUY = '700px'
15
- const MICRO = '120px'
16
- const BOTH = [MICRO, BUY]
17
- const BUY_ONLY = [BUY]
18
- const MICRO_ONLY = [MICRO]
19
-
20
- type DrawerMode = 'checkout' | 'added' | 'buy' | 'buy-added' | 'buy-checkout' | 'none' | 'closed' // manually
21
- type DrawerState = 'micro' | 'buy' | 'closed'
22
-
23
- const MODE_TO_STATE = {
24
- checkout: 'micro',
25
- added: 'micro',
26
- buy: 'buy',
27
- 'buy-checkout': 'buy',
28
- 'buy-added': 'buy',
29
- none: 'closed',
30
- closed: 'closed'
31
- } satisfies Record<DrawerMode, DrawerState>
32
-
33
- const MODE_TO_POINTS = {
34
- checkout: MICRO_ONLY,
35
- added: BOTH,
36
- buy: BUY_ONLY,
37
- 'buy-checkout': BOTH,
38
- 'buy-added': BOTH,
39
- none: BOTH,
40
- closed: BOTH
41
- }
42
-
43
-
44
- class ObsDrawerState {
45
-
46
- _mode: DrawerMode = 'none'
47
-
48
- constructor() {
49
- makeObservable(this, {
50
- _mode: observable,
51
- setMode: action,
52
- mode: computed,
53
- state: computed,
54
- points: computed,
55
- modal: computed,
56
- activePoint: computed
57
- })
58
- }
59
-
60
- get mode(): DrawerMode {return this._mode}
61
- get state(): DrawerState { return MODE_TO_STATE[this._mode] }
62
- get points(): (number | string)[] { return MODE_TO_POINTS[this._mode] }
63
- get modal(): boolean { return this.state !== 'micro' }
64
- get activePoint(): number | string | null {
65
- if (this.state === 'buy') return BUY
66
- if (this.state === 'micro') return MICRO
67
- return null
68
- }
69
-
70
- setMode = (m: DrawerMode) => {this._mode = m}
71
- }
72
-
73
11
  const CommerceUIComponent: React.FC = observer(() => {
74
12
 
75
- const cmmc = useCommerce()
76
13
  const ui = useCommerceUI()
77
14
  const router = useRouter()
78
- const isCheckout = usePathname() === '/checkout'
79
-
80
- const stateRef = useRef<ObsDrawerState>(new ObsDrawerState())
81
- const reactionDisposers = useRef<IReactionDisposer[]>([])
82
-
83
- const [activeSnapPoint, setActiveSnapPoint] = useState<string | number | null>(null)
84
- const setterRef = useRef<((index: number ) => void) | undefined>(undefined)
85
-
86
- useEffect(() => {
87
-
88
- reactionDisposers.current.push(reaction(
89
-
90
- () => ({
91
- buy: !!ui.buyOptionsSkuPath,
92
- added: !isCheckout && ui.item,
93
- checkout: !isCheckout && !cmmc.cartEmpty,
94
- closed: ui.closed
95
- }),
96
- ({buy, added, checkout, closed}) => {
97
- let mode: DrawerMode = 'none' // TODO: 'closed'
98
- if (buy) {
99
- if (added) {
100
- mode = 'buy-added'
101
- }
102
- else if (checkout) {
103
- mode = 'buy-checkout'
104
- }
105
- else {
106
- mode = 'buy'
107
- }
108
- }
109
- else {
110
- if (closed) {
111
- mode = 'closed'
112
- }
113
- else if (added) {
114
- mode = 'added'
115
- }
116
- else if (checkout) {
117
- mode = 'checkout'
118
- }
119
- }
120
- stateRef.current.setMode(mode)
121
- },
122
- {equals: (val, prev) => (
123
- val.buy === prev.buy
124
- &&
125
- val.added === prev.added
126
- &&
127
- val.checkout === prev.checkout
128
- &&
129
- val.closed === prev.closed
130
- )}
131
- )),
132
- reactionDisposers.current.push(reaction(
133
- () => ( stateRef.current.state ),
134
- (s) => {
135
- if (s === 'buy') {
136
- //setterRef.current?.(stateRef.current.points.length - 1)
137
- setActiveSnapPoint(BUY)
138
- }
139
- else if (s === 'micro') {
140
- //setterRef.current?.(0)
141
- setActiveSnapPoint(MICRO)
142
- }
143
- }
144
- ))
145
- return () => {
146
- reactionDisposers.current?.forEach((d) => {d()})
147
- }
148
- }, [isCheckout])
149
-
150
- const _setActiveSnapPoint = (pt: string | number | null): void => {
151
- console.log("ON CHANGE: ", pt)
152
- setActiveSnapPoint(pt)
153
- }
154
15
 
155
16
  const handleCheckout = () => {
156
17
  router.push('/checkout')
157
18
  }
158
19
 
159
- const reallyOnlyCloseDrawer = (b: boolean) => {
160
20
  // Should only ever be called internally to close
161
- // Using handleCloseGesture()
162
- }
163
-
164
- const handleHandleClicked = () => {
165
- console.log("HANDLE CLICKED")
166
-
167
- if (stateRef.current.state === 'buy') {
168
- const toks = stateRef.current.mode.split('-')
169
- if (toks.length <= 1) {
170
- console.log("CLOSING 'BUY' ... ")
171
- ui.hideBuyOptions()
172
- }
173
- else {
174
- console.log("CLOSING 'BUY' to ", toks[1])
175
- ui.hideBuyOptions()
176
- }
177
- }
178
- else if (stateRef.current.state === 'micro') {
179
- if (stateRef.current.mode === 'checkout') {
180
- console.log(" CLOSING 'CHECKOUT' ... ")
181
- ui.setClosed(true)
182
- }
183
- else if (stateRef.current.mode === 'added') {
184
- console.log(" OPENING 'ADDED' ... ")
185
- ui.showBuyOptions(ui.item?.sku ?? '')
186
- }
187
- }
188
- }
189
-
190
- const handleCloseGesture = () => {
191
- if (stateRef.current.state === 'buy') {
192
- console.log(" CLOSING 'BUY' ... ")
193
- const toks = stateRef.current.mode.split('-')
194
- if (toks.length <= 1) {
195
- stateRef.current.setMode('none')
196
- }
197
- else {
198
- stateRef.current.setMode(toks[1] as DrawerMode) // 'checkout' or 'added'
199
- }
200
- return true // "handled!"
21
+ const reallyOnlyCloseDrawer = (b: boolean) => {
22
+ if (!b ) {
23
+ ui.hideBuyOptions()
201
24
  }
202
- console.log("DEFAULT CLOSE ACTION")
203
- return false
204
25
  }
205
26
 
206
-
207
- const setActiveSPIndexSetter = (fn: (index: number ) => void): void => {
208
- setterRef.current = fn
209
- }
210
-
211
-
212
27
  return (
213
28
  <CommerceDrawer
214
- open={!(stateRef.current.state === 'closed')}
29
+ open={!!ui.buyOptionsSkuPath}
215
30
  setOpen={reallyOnlyCloseDrawer}
216
- drawerClx={'w-full h-full'}
217
- snapPoints={stateRef.current.points}
218
- modal={stateRef.current.modal}
219
- activeSnapPoint={activeSnapPoint}
220
- setActiveSnapPoint={_setActiveSnapPoint}
221
- handleHandleClicked={handleHandleClicked}
222
- setActiveSPIndexSetter={setActiveSPIndexSetter}
223
- handleCloseGesture={handleCloseGesture}
31
+ drawerClx={'w-full md:max-w-[550px] md:mx-auto lg:max-w-[50vw]'}
224
32
  >
225
- {stateRef.current.state === 'buy' && (
226
- <CarouselBuyCard
227
- skuPath={ui.buyOptionsSkuPath!}
228
- checkoutButton={
229
- <CheckoutButton handleCheckout={handleCheckout} className='w-full min-w-[160px] sm:max-w-[320px]'/>
230
- }
231
- onQuantityChanged={ui.itemQuantityChanged}
232
- clx='w-full'
233
- addBtnClx='w-full min-w-[160px] sm:max-w-[320px]'
234
- selectorClx='max-w-[475px]'
235
- />
236
- )}
237
- {stateRef.current.state === 'micro' && (
238
- <p>Mode: {stateRef.current.mode}</p>
239
- )}
33
+ <CarouselBuyCard
34
+ skuPath={ui.buyOptionsSkuPath!}
35
+ checkoutButton={
36
+ <CheckoutButton handleCheckout={handleCheckout} className='w-full min-w-[160px] sm:max-w-[320px]'/>
37
+ }
38
+ clx='w-full'
39
+ addBtnClx='w-full min-w-[160px] sm:max-w-[320px]'
40
+ selectorClx='max-w-[475px]'
41
+ />
240
42
  </CommerceDrawer>
241
43
  )
242
44
  })
@@ -2,6 +2,7 @@
2
2
  import React, { useEffect, useRef } from 'react'
3
3
  import { observable, type IObservableValue, reaction } from 'mobx'
4
4
  import { observer } from 'mobx-react-lite'
5
+ import { type LucideProps } from 'lucide-react'
5
6
 
6
7
  import { Button, type ButtonProps } from '@hanzo/ui/primitives'
7
8
  import { cn } from '@hanzo/ui/util'
@@ -11,15 +12,11 @@ import * as Icons from '../icons'
11
12
 
12
13
  const IconAndQuantity: React.FC<{
13
14
  animateOnQuantityChange?: boolean
14
- showArrow?: boolean
15
- showQuantity?: boolean
16
15
  clx?: string
17
16
  iconClx?: string
18
17
  digitClx?: string
19
18
  }> = observer(({
20
- animateOnQuantityChange=false,
21
- showArrow=true,
22
- showQuantity=true,
19
+ animateOnQuantityChange=true,
23
20
  clx='',
24
21
  iconClx='',
25
22
  digitClx=''
@@ -50,7 +47,6 @@ const IconAndQuantity: React.FC<{
50
47
 
51
48
  return (
52
49
  <div className={cn('flex items-center justify-center', clx)}>
53
- {showQuantity && (
54
50
  <div className={cn(
55
51
  'relative flex items-center justify-center mr-1',
56
52
  ((wiggleRef.current.get() === 'more') ?
@@ -64,13 +60,12 @@ const IconAndQuantity: React.FC<{
64
60
  'absolute left-0 right-0 top-0 bottom-0',
65
61
  digitClx
66
62
  )}>
67
- <div style={{/* color: 'white' tailwind bug? ,*/ fontSize: '11px', position: 'relative', top: '1px' }}>{cmmc.cartQuantity}</div>
63
+ <div style={{color: 'white' /* tailwind bug? */, fontSize: '11px', position: 'relative', top: '1px' }}>{cmmc.cartQuantity}</div>
68
64
  </div>
69
65
  )}
70
66
  <Icons.bag width='19' height='24' className={cn('relative -top-[3px] opacity-70' , iconClx)} aria-hidden="true" />
71
67
  </div>
72
- )}
73
- {showArrow && (<span style={{fontSize: '17px',}}>&rsaquo;</span>)}
68
+ <span style={{fontSize: '17px',}}>&rsaquo;</span>
74
69
  </div>
75
70
  )
76
71
  })
@@ -78,7 +73,6 @@ const IconAndQuantity: React.FC<{
78
73
  const CheckoutButton: React.FC<ButtonProps & {
79
74
  handleCheckout: () => void
80
75
  showQuantity?: boolean
81
- showArrow?: boolean
82
76
  animateOnQuantityChange?: boolean
83
77
  centerText?: boolean
84
78
  }> = ({
@@ -87,10 +81,8 @@ const CheckoutButton: React.FC<ButtonProps & {
87
81
  rounded='lg',
88
82
  className,
89
83
  showQuantity=true,
90
- showArrow=true,
91
84
  animateOnQuantityChange=true,
92
85
  centerText=true,
93
- children,
94
86
  ...rest
95
87
  }) => {
96
88
 
@@ -101,27 +93,22 @@ const CheckoutButton: React.FC<ButtonProps & {
101
93
  variant={variant}
102
94
  rounded={rounded}
103
95
  className={cn(
104
- 'flex justify-between items-stretch group',
105
- showQuantity ? (centerText ? 'px-1.5' : 'pl-2.5 pr-1.5') : '',
106
96
  className,
97
+ 'flex justify-between items-stretch',
98
+ showQuantity ? (centerText ? 'px-1.5' : 'pl-2.5 pr-1.5') : ''
107
99
  )}
108
100
  >
109
- {centerText && ( // must scale this one too, as it effects layout
101
+ {showQuantity && centerText && (
102
+ <IconAndQuantity clx='invisible' />
103
+ )}
104
+ <div className='flex justify-center items-center'>Checkout</div>
105
+ {showQuantity && (
110
106
  <IconAndQuantity
111
- showArrow={showArrow}
112
- showQuantity={showQuantity}
113
- clx='invisible group-hover:scale-105 transition-scale transition-duration-300'
107
+ animateOnQuantityChange={animateOnQuantityChange}
108
+ iconClx='fill-fg-foreground'
109
+ digitClx='text-primary-fg leading-none font-bold font-sans'
114
110
  />
115
111
  )}
116
- {children ?? (<div className='flex justify-center items-center'>Checkout</div>)}
117
- <IconAndQuantity
118
- clx='group-hover:scale-105 transition-scale transition-duration-300'
119
- animateOnQuantityChange={animateOnQuantityChange}
120
- showArrow={showArrow}
121
- showQuantity={showQuantity}
122
- iconClx='fill-background'
123
- digitClx='text-foreground group-hover:opacity-80 leading-none font-bold font-sans'
124
- />
125
112
  </Button>
126
113
  )
127
114
  }
@@ -1,104 +1,18 @@
1
1
  'use client'
2
- import React, { useRef } from 'react'
2
+ import React from 'react'
3
3
  import { createPortal } from 'react-dom'
4
4
  import { usePathname, useRouter } from 'next/navigation'
5
5
  import { observer } from 'mobx-react-lite'
6
6
 
7
7
  import { cn } from '@hanzo/ui/util'
8
- import { useStepAnimation } from '@hanzo/ui/util-client'
9
-
10
8
  import { Image } from '@hanzo/ui/primitives'
11
9
 
12
- import { useCommerceUI } from '../../../commerce/ui-context'
10
+ import { useCommerceUI } from '@hanzo/commerce'
13
11
 
14
12
  import CheckoutButton from '../checkout-button'
15
13
  import useAnimationClxSet from './use-anim-clx-set'
14
+ import useLaggingItemRef from './use-lagging-item-ref'
16
15
  import CONST from './const'
17
- import type { LineItem } from '@hanzo/commerce/types'
18
-
19
- const transStyle = (t: { transition: string, from : string, to: string } | undefined) : any => (
20
- t ? {
21
- transitionProperty: t.transition,
22
- transitionTimingFunction: CONST.animTimingFn,
23
- transitionDuration: `${CONST.animDurationMs}ms`
24
- } : {}
25
- )
26
-
27
- const transClx = (on: boolean, t: { transition: string, from : string, to: string } | undefined) : string => (
28
- on ? (t?.from ?? '') : (t?.to ?? '')
29
- )
30
-
31
- const VARS: any = {
32
- BR: {
33
- pos: 'bottom-[24px] right-[66px]',
34
- width: 'w-initial',
35
- centerText: false,
36
- coClx: 'w-auto',
37
- infoClx: 'w-auto',
38
- activeItemAnim: {
39
- co: {
40
- transition: 'none',
41
- from : 'px-3 gap-2.5',
42
- to: ''
43
- },
44
- coText: {
45
- transition: 'max-width',
46
- from : 'max-w-[100px]',
47
- to: 'max-w-[0px]'
48
- },
49
- info: {
50
- transition: 'transform, opacity',
51
- from : 'scale-x-100 opacity-100 origin-right',
52
- to: 'scale-x-0 opacity-0 origin-right'
53
- }
54
- },
55
- showArrow: true
56
- },
57
- TR: {
58
- pos: 'top-[48px] md:top-[80px] right-[28px]',
59
- width: 'w-initial',
60
- centerText: false,
61
- showQuantity: false,
62
- showArrow: true,
63
- coClx: 'w-auto px-3 gap-1',
64
- infoClx: 'w-auto',
65
- activeItemAnim: {
66
- co: {
67
- transition: 'none',
68
- from : 'px-3 gap-2.5',
69
- to: ''
70
- },
71
- coText: {
72
- transition: 'max-width',
73
- from : 'max-w-[100px]',
74
- to: 'max-w-[0px]'
75
- },
76
- info: {
77
- transition: 'transform',
78
- from : 'scale-x-100 origin-right',
79
- to: 'scale-x-0 origin-right'
80
- }
81
- },
82
- },
83
- TRIO: {
84
- pos: 'top-[48px] md:top-[70px] right-[28px]',
85
- centerText: false,
86
- showQuantity: true,
87
- showArrow: true,
88
- width: 'w-initial',
89
- coClx: 'hidden',
90
- infoClx: 'w-auto',
91
- activeItemAnim: {
92
- info: {
93
- transition: 'transform, opacity',
94
- from : 'scale-x-100 opacity-100',
95
- to: 'scale-x-50 opacity-0'
96
- }
97
- },
98
- }
99
- }
100
-
101
- const v = 'TR'
102
16
 
103
17
  const CheckoutWidget: React.FC<{
104
18
  clx?: string
@@ -112,78 +26,58 @@ const CheckoutWidget: React.FC<{
112
26
  const clxSet = useAnimationClxSet(isCheckout)
113
27
 
114
28
  const itemRef = useCommerceUI()
115
-
116
- // for rendering content after itemRef.item() would return false
117
- const persistentRef = useRef<LineItem | undefined>(undefined)
118
-
119
- // Doing double duty of being initial step fn for StepAnimation,
120
- // and also capturing the item for persistentRef :)
121
- const initialStepFn = (): boolean => {
122
- if (!!itemRef.item && !persistentRef.current) {
123
- persistentRef.current = itemRef.item
124
- }
125
- return !!itemRef.item
126
- }
127
- const steps = useStepAnimation(initialStepFn, [CONST.animDurationMs, CONST.animDurationMs, CONST.animDurationMs])
29
+ const laggingRef = useLaggingItemRef(itemRef, CONST.animDurationMs)
128
30
 
129
31
  const handleCheckout = () => { router.push('/checkout')}
130
32
 
131
33
  return globalThis?.document?.body && createPortal(
132
34
  (<div
133
35
  className={cn(
134
- VARS[v].width,
135
- 'z-below-modal-2 fixed ',
136
- VARS[v].pos,
137
- 'rounded-lg',
36
+ 'min-w-[160px] sm:max-w-[320px] w-[calc(100%-72px)] ml-2 !h-10',
37
+ 'z-below-modal-2 fixed bottom-[20px] left-0 right-0',
38
+ 'rounded-lg bg-background',
138
39
  'flex',
139
- steps.notPast(0) ? 'bg-background' : '',
140
- steps.notPast(1) ? 'gap-2' : '',
40
+ itemRef.item ? 'gap-2' : '',
141
41
  clxSet.asArray.join(' ')
142
42
  )}
143
- style={steps.notPast(1) ? {} : VARS[v].coClx?.includes('hidden') ? {} : CONST.shadowStyle}
43
+ style={laggingRef.item ? {} : CONST.shadowStyle}
144
44
  >
145
45
  <div
146
46
  className={cn(
147
47
  'flex flex-row justify-between items-center',
148
- transClx(steps.notPast(0), VARS[v].activeItemAnim.info),
149
- VARS[v].itemClx,
150
- steps.notPast(1) ? 'px-3 border rounded-lg bg-level-1 border-muted-3' : ''
48
+ itemRef.item ? CONST.compWidthClx.itemInfo : 'w-0',
49
+ laggingRef.item ? 'px-3 border rounded-lg border-muted-3' : ''
151
50
  )}
152
- style={transStyle(VARS[v].activeItemAnim.info)}
51
+ style={{
52
+ transitionProperty: 'width',
53
+ transitionTimingFunction: CONST.animTimingFn,
54
+ transitionDuration: `${CONST.animDurationMs}ms`
55
+ }}
153
56
  >
154
- {steps.notPast(1) && persistentRef.current?.img && (
155
- <Image def={persistentRef.current.img} constrainTo={CONST.itemImgConstraint} preload className='grow-0 shrink-0'/>
156
- )}
157
- {steps.notPast(1) && persistentRef.current && (<div className='text-foreground grow ml-1'>
158
- <p className='whitespace-nowrap text-ellipsis text-sm'>{persistentRef.current.title}</p>
159
- <p className='whitespace-nowrap text-clip text-xxs' >recently added...</p>
160
- </div>)}
57
+ {laggingRef.item?.img ? (
58
+ <Image def={laggingRef.item.img} constrainTo={CONST.itemImgConstraint} preload className='grow-0 shrink-0'/>
59
+ ) : ( // placeholder so things align
60
+ <div style={{height: CONST.itemImgConstraint.h, width: CONST.itemImgConstraint.w}} className='bg-level-3 grow-0 shrink-0'/>
61
+ )}
62
+
63
+ <div className='text-muted grow ml-1'>
64
+ {laggingRef.item && (<>
65
+ <p className='whitespace-nowrap text-sm'>{laggingRef.item.title}</p>
66
+ <p className='whitespace-nowrap text-xxs' >recently added...</p>
67
+ </>)}
68
+ </div>
161
69
  </div>
162
70
  <CheckoutButton
163
71
  handleCheckout={handleCheckout}
164
- centerText={VARS[v].centerText ?? !steps.notPast(0)}
165
- variant='primary'
166
- rounded='lg'
167
- showQuantity={VARS[v].showQuantity ?? true}
168
- showArrow={VARS[v].showArrow ?? true}
169
- className={cn(
170
- // for setting and unsetting 'gap'
171
- transClx((VARS[v].activeItemAnim.coText ? steps.notPast(3) : true), VARS[v].activeItemAnim.co),
172
- VARS[v].coClx
173
- )}
174
- style={transStyle(VARS[v].activeItemAnim.co)}
175
- >
176
- <div
177
- className={cn(
178
- 'overflow-hidden',
179
- 'flex justify-center items-center',
180
- transClx(steps.notPast(2), VARS[v].activeItemAnim.coText),
181
- )}
182
- style={transStyle(VARS[v].activeItemAnim.coText)}
183
- >
184
- Checkout
185
- </div>
186
- </CheckoutButton>
72
+ centerText={!!!itemRef.item}
73
+ variant='primary' rounded='lg'
74
+ className={cn(itemRef.item ? CONST.compWidthClx.checkout : 'w-full')}
75
+ style={{
76
+ transitionProperty: 'width',
77
+ transitionTimingFunction: CONST.animTimingFn,
78
+ transitionDuration: `${CONST.animDurationMs}ms`
79
+ }}
80
+ />
187
81
  </div>),
188
82
  globalThis?.document?.body
189
83
  )
@@ -1,10 +1,8 @@
1
1
  import { useEffect, useRef } from 'react'
2
2
  import { reaction, runInAction} from 'mobx'
3
3
 
4
- import { useCommerce } from '@hanzo/commerce'
5
-
6
4
  import ObsStringSet from './obs-string-set'
7
- import { useCommerceUI } from '../../../commerce/ui-context'
5
+ import { useCommerce, useCommerceUI } from '@hanzo/commerce'
8
6
 
9
7
  export default (isCheckout: boolean): ObsStringSet => {
10
8
 
@@ -0,0 +1,30 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import { reaction } from 'mobx'
3
+
4
+ import type { LineItem, ObsLineItemRef } from "@hanzo/commerce/types"
5
+ import { LineItemRef } from '@hanzo/commerce'
6
+
7
+ export default (orig: ObsLineItemRef, lagMs: number): ObsLineItemRef => {
8
+
9
+ // a ref that is synced to 'orig', but persists for lagMs longer
10
+ // so ui does not jump while animating out.
11
+ // (Fascilitates for start and end states in animation)
12
+ const laggingRef = useRef<LineItemRef>(new LineItemRef())
13
+
14
+ useEffect(() => (
15
+ reaction(
16
+ () => (orig.item),
17
+ (item: LineItem | undefined) => {
18
+ if (item) {
19
+ laggingRef.current.set(item)
20
+ }
21
+ else {
22
+ setTimeout(() => { laggingRef.current.set(undefined) }, lagMs)
23
+ }
24
+ },
25
+ {equals: (val, prev) => (val?.sku === prev?.sku)}
26
+ )
27
+ ), [])
28
+
29
+ return laggingRef.current
30
+ }
@@ -29,7 +29,7 @@ const DesktopHeader: React.FC<{
29
29
  {/* md or larger */}
30
30
  <div className={
31
31
  'flex flex-row h-[80px] items-center justify-between ' +
32
- 'px-[32px] w-full 2xl:mx-auto max-w-screen-2xl'
32
+ 'px-[8px] w-full mx-auto max-w-screen'
33
33
  }>
34
34
  <Logo size='md' href='/' className='hidden lg:flex' key='two' layout='text-only'/>
35
35
  <Logo size='sm' href='/' className='hidden md:flex lg:hidden' key='one' layout='text-only'/>
@@ -10,12 +10,18 @@ export { default as MiniChart } from './mini-chart'
10
10
  export { default as NotFound } from './not-found'
11
11
 
12
12
  export { default as AuthListener } from './auth/auth-listener'
13
- export { default as AddWidget } from './commerce/add-widget'
14
13
  export { default as BuyDrawer } from './commerce/buy-drawer'
15
- export { default as BuyButton } from './commerce/buy-button'
16
14
  export { default as CheckoutButton } from './commerce/checkout-button'
17
15
  export { default as CheckoutPanel } from './commerce/checkout-panel'
18
16
  export { default as CheckoutWidget } from './commerce/checkout-widget'
19
17
  export { default as LoginPanel } from './auth/login-panel'
20
18
  export { default as Scripts } from './scripts'
21
19
 
20
+
21
+ /* PLEASE KEEP
22
+ export {
23
+ default as HeadMetadata,
24
+ getTitleFromTemplateString,
25
+ TwitterComponent
26
+ } from './head-metadata'
27
+ */
package/conf/index.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import type { SelectionUISpecifier } from '@hanzo/commerce/types'
2
2
 
3
- export { default as commerceServiceOptions } from './lux-commerce-options'
4
-
5
3
  export const selectionUISpecifiers = {
6
4
 
7
5
  'LXM-CN': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luxfi/core",
3
- "version": "5.0.9",
3
+ "version": "5.1.1",
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/",
@@ -28,7 +28,6 @@
28
28
  "exports": {
29
29
  ".": "./components/index.ts",
30
30
  "./commerce": "./commerce/index.ts",
31
- "./commerce-data": "./commerce/data/index.ts",
32
31
  "./root-layout": "./root-layout/index.tsx",
33
32
  "./server-actions": "./server-actions/index.ts",
34
33
  "./next": "./next/index.ts",
@@ -39,9 +38,10 @@
39
38
  },
40
39
  "dependencies": {
41
40
  "@hanzo/auth": "2.4.6",
42
- "@hanzo/commerce": "7.0.4",
43
- "@hanzo/ui": "3.7.23",
41
+ "@hanzo/commerce": "7.0.2",
42
+ "@hanzo/ui": "3.7.0",
44
43
  "@next/third-parties": "^14.1.0",
44
+ "@types/node": "^20.12.12",
45
45
  "cookies-next": "^4.1.1",
46
46
  "date-fns": "^3.6.0",
47
47
  "embla-carousel-autoplay": "^8.0.1",
@@ -51,15 +51,13 @@
51
51
  "peerDependencies": {
52
52
  "@hookform/resolvers": "^3.3.2",
53
53
  "lucide-react": "^0.344.0",
54
- "mobx": "^6.12.0",
55
- "mobx-react-lite": "^4.0.5",
56
54
  "next": "14.1.3",
57
55
  "next-themes": "^0.2.1",
58
- "react": "^18.3.1",
59
- "react-dom": "^18.3.1",
60
- "react-hook-form": "^7.51.4",
56
+ "react": "^18.2.0",
57
+ "react-dom": "^18.2.0",
58
+ "react-hook-form": "^7.44.2",
61
59
  "validator": "^13.11.0",
62
- "zod": "3.23.8"
60
+ "zod": "3.21.4"
63
61
  },
64
62
  "devDependencies": {
65
63
  "@mdx-js/loader": "^3.0.0",
@@ -67,8 +65,8 @@
67
65
  "@types/facebook-pixel": "^0.0.30",
68
66
  "@types/gtag.js": "^0.0.19",
69
67
  "@types/mdx": "^2.0.9",
70
- "@types/react": "^18.3.2",
71
- "@types/react-dom": "^18.3.0",
68
+ "@types/react": "^18.2.64",
69
+ "@types/react-dom": "^18.2.18",
72
70
  "tailwindcss": "^3.4.2",
73
71
  "typescript": "5.3.3"
74
72
  }
@@ -10,10 +10,9 @@ import { CommerceProvider } from '@hanzo/commerce'
10
10
  import getAppRouterBodyFontClasses from '../next/font/get-app-router-font-classes'
11
11
  import { FacebookPixelHead } from '../next/analytics/pixel-analytics'
12
12
 
13
- import { CommerceUIProvider } from '../commerce/ui-context'
14
13
  import { AuthListener, ChatWidget, Header, Scripts } from '../components'
15
-
16
14
  import BuyDrawer from '../components/commerce/buy-drawer'
15
+ import CheckoutWidget from '../components/commerce/checkout-widget'
17
16
 
18
17
  import { selectionUISpecifiers } from '../conf'
19
18
  import type SiteDef from '../types/site-def'
@@ -100,10 +99,9 @@ const RootLayout: React.FC<PropsWithChildren & {
100
99
  options={siteDef.commerce!.options}
101
100
  uiSpecs={selectionUISpecifiers}
102
101
  >
103
- <CommerceUIProvider >
104
- <Guts />
105
- <BuyDrawer />
106
- </CommerceUIProvider>
102
+ <Guts />
103
+ <BuyDrawer />
104
+ <CheckoutWidget />
107
105
  </CommerceProvider>
108
106
  ) : (
109
107
  <Guts />
package/tsconfig.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
- "extends": "../tsconfig.modules.base.json",
2
+ "extends": "../tsconfig.lux.base.json",
3
3
  "include": [
4
4
  "**/*.ts",
5
- "**/*.tsx"
5
+ "**/*.tsx",
6
6
  ],
7
7
  "exclude": [
8
- "node_modules"
9
- ]
8
+ "node_modules",
9
+ ],
10
10
  }
@@ -1,118 +0,0 @@
1
- import {
2
- action,
3
- computed,
4
- makeObservable,
5
- observable,
6
- } from 'mobx'
7
- import type { CommerceService, LineItem, ObsLineItemRef } from '@hanzo/commerce/types'
8
-
9
-
10
- interface CommerceUI extends ObsLineItemRef {
11
- showBuyOptions: (skuPath: string) => void
12
- hideBuyOptions: () => void
13
- get buyOptionsSkuPath(): string | undefined
14
-
15
- itemQuantityChanged(sku: string, val: number, prevVal: number): void
16
-
17
- get closed(): boolean
18
- setClosed(b: boolean): void
19
-
20
- }
21
-
22
- class CommerceUIStore implements CommerceUI {
23
-
24
- static readonly TIMEOUT = 1500
25
- _buyOptionsSkuPath: string | undefined = undefined
26
- _closed: boolean = false
27
- _paused: boolean = false
28
- _activeItem: LineItem | undefined = undefined
29
- _lastActivity: number | undefined = undefined
30
- _service: CommerceService
31
-
32
- constructor(s: CommerceService) {
33
- this._service = s
34
- makeObservable(this, {
35
- _buyOptionsSkuPath: observable,
36
- _activeItem: observable.shallow,
37
- _closed: observable,
38
- showBuyOptions: action,
39
- hideBuyOptions: action,
40
- buyOptionsSkuPath: computed,
41
- itemQuantityChanged: action,
42
- setClosed: action,
43
- closed: computed,
44
- tick: action,
45
- item: computed
46
- })
47
- }
48
-
49
- showBuyOptions = (skuPath: string): void => {
50
- this._service.setCurrentItem(undefined)
51
- this._buyOptionsSkuPath = skuPath
52
- this._paused = true
53
- this._closed = false
54
- }
55
-
56
- hideBuyOptions = (): void => {
57
- this._buyOptionsSkuPath = undefined
58
- this._paused = false
59
- if (this._lastActivity) {
60
- this._lastActivity = Date.now()
61
- }
62
- }
63
-
64
- get buyOptionsSkuPath(): string | undefined {
65
- return this._buyOptionsSkuPath
66
- }
67
-
68
- tick = () => {
69
- if (
70
- !this._paused
71
- &&
72
- this._lastActivity
73
- &&
74
- (Date.now() - this._lastActivity >= CommerceUIStore.TIMEOUT)
75
- ) {
76
- this._activeItem = undefined
77
- this._lastActivity = undefined
78
- }
79
- }
80
-
81
- itemQuantityChanged = (sku: string, val: number, oldVal: number): void => {
82
-
83
- if (val === 0) {
84
- if (this._activeItem?.sku === sku) {
85
- this._activeItem = undefined
86
- this._lastActivity = undefined
87
- }
88
- // otherwise ignore
89
- }
90
- else if (val < oldVal) {
91
- if (this._activeItem?.sku === sku) {
92
- this._lastActivity = Date.now()
93
- }
94
- // otherwise ignore
95
- }
96
- else {
97
- this._activeItem = this._service.getItemBySku(sku)
98
- this._lastActivity = Date.now()
99
- }
100
- }
101
-
102
- get item(): LineItem | undefined {
103
- return this._activeItem
104
- }
105
-
106
- get closed(): boolean {
107
- return this._closed
108
- }
109
-
110
- setClosed = (b: boolean): void => { this._closed = b}
111
-
112
- dispose = () => {}
113
- }
114
-
115
- export {
116
- CommerceUIStore,
117
- type CommerceUI
118
- }
@@ -1,50 +0,0 @@
1
- 'use client'
2
- import React, {
3
- createContext,
4
- useContext,
5
- useRef,
6
- type PropsWithChildren,
7
- useEffect
8
- } from 'react'
9
-
10
- // https://dev.to/ivandotv/mobx-server-side-rendering-with-next-js-4m18
11
- import { enableStaticRendering } from 'mobx-react-lite'
12
- enableStaticRendering(typeof window === "undefined")
13
-
14
- import { type CommerceUI, CommerceUIStore } from './commerce-ui'
15
- import { useCommerce } from '@hanzo/commerce'
16
-
17
- const CommerceUIContext = createContext<CommerceUIStore | undefined>(undefined)
18
-
19
- const useCommerceUI = (): CommerceUI => {
20
- return useContext(CommerceUIContext) as CommerceUIStore
21
- }
22
-
23
- const CommerceUIProvider: React.FC<PropsWithChildren & {
24
- DEBUG_NO_TICK?: boolean
25
- }> = ({
26
- children,
27
- DEBUG_NO_TICK=false
28
- }) => {
29
-
30
- const cmmc = useCommerce()
31
- const valueRef = useRef<CommerceUIStore>(new CommerceUIStore(cmmc))
32
-
33
- useEffect(() => {
34
-
35
- //valueRef.current = new CommerceUIStore(cmmc)
36
- return () => { valueRef.current?.dispose() }
37
- }, [])
38
-
39
- return (
40
- <CommerceUIContext.Provider value={valueRef.current}>
41
- {children}
42
- </CommerceUIContext.Provider>
43
- )
44
- }
45
-
46
- export {
47
- useCommerceUI,
48
- CommerceUIProvider
49
- }
50
-
@@ -1,20 +0,0 @@
1
- 'use client'
2
- import React from 'react'
3
-
4
- import type { LineItem } from '@hanzo/commerce/types'
5
- import { AddToCartWidget } from '@hanzo/commerce'
6
-
7
- import { useCommerceUI } from '../../commerce/ui-context'
8
-
9
- const AddWidget: React.FC<{
10
- item: LineItem
11
- disabled?: boolean
12
- className?: string
13
- buttonClx?: string
14
- variant?: 'minimal' | 'primary' | 'outline'
15
- }> = (props) => {
16
- const ui = useCommerceUI()
17
- return <AddToCartWidget {...props} onQuantityChanged={ui.itemQuantityChanged}/>
18
- }
19
-
20
- export default AddWidget
@@ -1,34 +0,0 @@
1
- 'use client'
2
- import React, {type PropsWithChildren} from 'react'
3
-
4
- import { Button, buttonVariants } from '@hanzo/ui/primitives'
5
- import { type VariantProps } from '@hanzo/ui/util'
6
-
7
- import { cn } from '@hanzo/ui/util'
8
- import { useCommerceUI } from '../../commerce/ui-context'
9
-
10
- const BuyButton: React.FC<
11
- PropsWithChildren &
12
- VariantProps<typeof buttonVariants> &
13
- {
14
- skuPath: string
15
- className?: string
16
- }
17
- > = ({
18
- skuPath,
19
- children,
20
- className='',
21
- ...rest
22
- }) => {
23
-
24
- const ui = useCommerceUI()
25
- const handleClick = () => { ui.showBuyOptions(skuPath) }
26
-
27
- return (
28
- <Button onClick={handleClick} {...rest} className={cn(className, '')}>
29
- {children}
30
- </Button>
31
- )
32
- }
33
-
34
- export default BuyButton
@@ -1,154 +0,0 @@
1
- [vaul-drawer] {
2
- touch-action: none;
3
- transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
4
- }
5
-
6
- [vaul-drawer][vaul-drawer-direction='bottom'] {
7
- transform: translate3d(0, 100%, 0);
8
- }
9
-
10
- [vaul-drawer][vaul-drawer-direction='top'] {
11
- transform: translate3d(0, -100%, 0);
12
- }
13
-
14
- [vaul-drawer][vaul-drawer-direction='left'] {
15
- transform: translate3d(-100%, 0, 0);
16
- }
17
-
18
- [vaul-drawer][vaul-drawer-direction='right'] {
19
- transform: translate3d(100%, 0, 0);
20
- }
21
-
22
- .vaul-dragging .vaul-scrollable [vault-drawer-direction='top'] {
23
- overflow-y: hidden !important;
24
- }
25
- .vaul-dragging .vaul-scrollable [vault-drawer-direction='bottom'] {
26
- overflow-y: hidden !important;
27
- }
28
-
29
- .vaul-dragging .vaul-scrollable [vault-drawer-direction='left'] {
30
- overflow-x: hidden !important;
31
- }
32
-
33
- .vaul-dragging .vaul-scrollable [vault-drawer-direction='right'] {
34
- overflow-x: hidden !important;
35
- }
36
-
37
- [vaul-drawer][vaul-drawer-visible='true'][vaul-drawer-direction='top'] {
38
- transform: translate3d(0, var(--snap-point-height, 0), 0);
39
- }
40
-
41
- [vaul-drawer][vaul-drawer-visible='true'][vaul-drawer-direction='bottom'] {
42
- transform: translate3d(0, var(--snap-point-height, 0), 0);
43
- }
44
-
45
- [vaul-drawer][vaul-drawer-visible='true'][vaul-drawer-direction='left'] {
46
- transform: translate3d(var(--snap-point-height, 0), 0, 0);
47
- }
48
-
49
- [vaul-drawer][vaul-drawer-visible='true'][vaul-drawer-direction='right'] {
50
- transform: translate3d(var(--snap-point-height, 0), 0, 0);
51
- }
52
-
53
- [vaul-overlay] {
54
- opacity: 0;
55
- transition: opacity 0.5s cubic-bezier(0.32, 0.72, 0, 1);
56
- }
57
-
58
- [vaul-overlay][vaul-drawer-visible='true'] {
59
- opacity: 1;
60
- }
61
-
62
- [vaul-drawer]::after {
63
- content: '';
64
- position: absolute;
65
- background: inherit;
66
- background-color: inherit;
67
- }
68
-
69
- [vaul-drawer][vaul-drawer-direction='top']::after {
70
- top: initial;
71
- bottom: 100%;
72
- left: 0;
73
- right: 0;
74
- height: 200%;
75
- }
76
-
77
- [vaul-drawer][vaul-drawer-direction='bottom']::after {
78
- top: 100%;
79
- bottom: initial;
80
- left: 0;
81
- right: 0;
82
- height: 200%;
83
- }
84
-
85
- [vaul-drawer][vaul-drawer-direction='left']::after {
86
- left: initial;
87
- right: 100%;
88
- top: 0;
89
- bottom: 0;
90
- width: 200%;
91
- }
92
-
93
- [vaul-drawer][vaul-drawer-direction='right']::after {
94
- left: 100%;
95
- right: initial;
96
- top: 0;
97
- bottom: 0;
98
- width: 200%;
99
- }
100
-
101
- [vaul-handle] {
102
- /* opacity: 0.8; */
103
- touch-action: pan-y;
104
- cursor: grab;
105
- }
106
-
107
- /* [vaul-handle]:hover, */
108
- [vaul-handle]:active {
109
- opacity: 1;
110
- }
111
-
112
- [vaul-handle]:active {
113
- cursor: grabbing;
114
- }
115
-
116
- [vaul-handle-hitarea] {
117
- position: absolute;
118
- left: 50%;
119
- top: 50%;
120
- transform: translate(-50%, -50%);
121
- width: max(100%, 2.75rem); /* 44px */
122
- height: max(100%, 2.75rem); /* 44px */
123
- touch-action: inherit;
124
- }
125
-
126
- [vaul-overlay][vaul-snap-points='true']:not([vaul-snap-points-overlay='true']):not([data-state='closed']) {
127
- opacity: 0;
128
- }
129
-
130
- [vaul-overlay][vaul-snap-points-overlay='true']:not([vaul-drawer-visible='false']) {
131
- opacity: 1;
132
- }
133
-
134
- /* This will allow us to not animate via animation, but still benefit from delaying unmount via Radix. */
135
- @keyframes fake-animation {
136
- from {
137
- }
138
- to {
139
- }
140
- }
141
-
142
- @media (pointer: fine) {
143
- [vaul-handle-hitarea] {
144
- width: 100%;
145
- height: 100%;
146
- }
147
- }
148
-
149
- @media (hover: hover) and (pointer: fine) {
150
- [vaul-drawer] {
151
- user-select: none;
152
- }
153
- }
154
-