@luxfi/ui 5.5.4 → 5.5.6

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.
Files changed (80) hide show
  1. package/commerce/ui/context.tsx +13 -16
  2. package/components/access-code-input.tsx +3 -4
  3. package/components/analytics.tsx +8 -17
  4. package/components/auth/callback.tsx +40 -0
  5. package/components/auth/login-panel.tsx +22 -50
  6. package/components/auth/login.tsx +47 -0
  7. package/components/auth/provider.tsx +34 -0
  8. package/components/auth/resume.ts +16 -0
  9. package/components/auth/widget.tsx +80 -0
  10. package/components/back-button.tsx +4 -7
  11. package/components/chat-widget.tsx +9 -11
  12. package/components/commerce/bag-button.tsx +8 -7
  13. package/components/commerce/buy-button.tsx +2 -3
  14. package/components/commerce/checkout-button.tsx +12 -10
  15. package/components/commerce/checkout-panel/cart-accordian.tsx +3 -4
  16. package/components/commerce/checkout-panel/desktop-cp.tsx +7 -8
  17. package/components/commerce/checkout-panel/index.tsx +1 -1
  18. package/components/commerce/checkout-panel/mobile-cp.tsx +5 -5
  19. package/components/commerce/checkout-panel/policy-links.tsx +7 -12
  20. package/components/commerce/checkout-panel/steps-indicator.tsx +1 -1
  21. package/components/commerce/desktop-bag-popup.tsx +1 -1
  22. package/components/commerce/drawer/micro.tsx +30 -30
  23. package/components/commerce/drawer/shell.tsx +3 -5
  24. package/components/contact-dialog/contact-form.tsx +6 -13
  25. package/components/contact-dialog/index.tsx +2 -2
  26. package/components/copyright.tsx +3 -2
  27. package/components/drawer-margin.tsx +2 -2
  28. package/components/footer.tsx +7 -8
  29. package/components/header/desktop-nav-menu.tsx +6 -6
  30. package/components/header/desktop.tsx +9 -9
  31. package/components/header/index.tsx +2 -2
  32. package/components/header/mobile-bag-drawer.tsx +1 -1
  33. package/components/header/mobile-menu-toggle-button.tsx +5 -6
  34. package/components/header/mobile-nav-menu-ai.tsx +14 -13
  35. package/components/header/mobile-nav-menu-item.tsx +12 -11
  36. package/components/header/mobile-nav-menu.tsx +19 -18
  37. package/components/header/mobile.tsx +10 -11
  38. package/components/icons/index.ts +0 -1
  39. package/components/icons/social-icon.tsx +1 -1
  40. package/components/index.ts +2 -2
  41. package/components/logo.tsx +40 -67
  42. package/components/main.tsx +3 -4
  43. package/components/mini-chart/mini-chart.tsx +2 -1
  44. package/components/security/index.tsx +55 -0
  45. package/next/font/font-desc.ts +18 -0
  46. package/next/font/fonts.ts +40 -0
  47. package/next/font/get-app-router-font-classes.ts +14 -12
  48. package/next/font/load-and-return-lux-next-fonts-on-import.ts +16 -24
  49. package/next/font/local/Zen-Variable.woff2 +0 -0
  50. package/next/font/next-font-desc.ts +18 -17
  51. package/next/head-metadata/index.tsx +7 -6
  52. package/next/head-metadata/next-metadata.ts +49 -0
  53. package/next/middleware/determine-device-mw.ts +0 -13
  54. package/package.json +15 -15
  55. package/root-layout/index.tsx +42 -29
  56. package/style/fonts/Zen-Variable.woff2 +0 -0
  57. package/style/lux-colors.css +0 -1
  58. package/style/lux-fonts.css +5 -11
  59. package/style/lux-global-non-next.css +9 -9
  60. package/style/lux-global.css +99 -15
  61. package/tsconfig.json +8 -0
  62. package/types/site-def.ts +7 -0
  63. package/components/auth/auth-listener.tsx +0 -33
  64. package/components/auth/auth-token/clear-auth-token.tsx +0 -12
  65. package/components/auth/auth-token/set-auth-token.tsx +0 -16
  66. package/components/auth/common-auth-domains.ts +0 -17
  67. package/components/auth/mobile-login-button.tsx +0 -112
  68. package/components/auth/signup-panel.tsx +0 -113
  69. package/components/icons/lux-logo.tsx +0 -10
  70. package/next/font/local/Druk-Wide-Bold.ttf +0 -0
  71. package/next/font/local/Druk-Wide-Medium.ttf +0 -0
  72. package/next/head-metadata/from-next/metadata-types.ts +0 -158
  73. package/next/head-metadata/from-next/opengraph-types.ts +0 -267
  74. package/next/head-metadata/from-next/twitter-types.ts +0 -92
  75. package/style/fonts/Druk-Wide-Bold.ttf +0 -0
  76. package/style/fonts/Druk-Wide-Medium.ttf +0 -0
  77. package/tailwind/fontFamily.tailwind.lux.ts +0 -18
  78. package/tailwind/index.ts +0 -2
  79. package/tailwind/lux-tw-fonts.ts +0 -40
  80. package/tailwind/tailwind.config.lux-preset.ts +0 -10
@@ -12,13 +12,17 @@ import { usePathname } from 'next/navigation'
12
12
  import { useDebounceCallback } from 'usehooks-ts'
13
13
 
14
14
 
15
- import { preset as twConfig } from '@hanzo/ui/tailwind'
16
15
  import { useCommerce } from '@hanzo/commerce'
17
16
 
18
17
  import type { CommerceDrawer, SelectAndBuy } from './store'
19
18
  import { CommerceUIStore } from './store'
20
19
  import conf from './conf'
21
20
 
21
+ /** The `md` rung: below this the commerce drawer behaves as a mobile sheet.
22
+ * It used to be read out of a Tailwind preset's `screens.md`, which is one
23
+ * more place for a number that belongs in one. */
24
+ const DESKTOP_MIN = 768
25
+
22
26
  const LOG = false ////////////////////
23
27
  const log = (s: string) => {
24
28
  if (LOG) {
@@ -48,23 +52,16 @@ const CommerceUIProvider: React.FC<PropsWithChildren> = ({
48
52
  const storeRef = useRef<CommerceUIStore>(new CommerceUIStore(cmmc, conf))
49
53
  const prevPathRef = useRef<string>('initial')
50
54
 
51
- const onResize = () => {
52
- const width = window.innerWidth
53
- let desktopMin = 0
54
- if (twConfig.theme?.screens) {
55
- // expected form: { md: '768px' }
56
- if ('md' in twConfig.theme?.screens && typeof twConfig.theme?.screens.md === 'string') {
57
- desktopMin = parseInt(twConfig.theme?.screens.md)
58
- }
59
- if (width < desktopMin) {
60
- if (!storeRef.current.isMobile) {
61
- storeRef.current.setMobile(true)
62
- }
63
- }
64
- else if (storeRef.current.isMobile) {
65
- storeRef.current.setMobile(false)
55
+ const onResize = () => {
56
+ const width = window.innerWidth
57
+ if (width < DESKTOP_MIN) {
58
+ if (!storeRef.current.isMobile) {
59
+ storeRef.current.setMobile(true)
66
60
  }
67
61
  }
62
+ else if (storeRef.current.isMobile) {
63
+ storeRef.current.setMobile(false)
64
+ }
68
65
  storeRef.current.setViewportHeight(window.innerHeight)
69
66
  }
70
67
 
@@ -2,8 +2,7 @@
2
2
 
3
3
  import { useState } from 'react'
4
4
  import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from '@hanzo/ui/primitives'
5
- import { cn } from '@hanzo/ui/util'
6
-
5
+ import { Box, cn } from '@hanzo/ui'
7
6
  const AccessCodeInput: React.FC<{
8
7
  onSuccess?: () => void
9
8
  onFail?: () => void
@@ -35,7 +34,7 @@ const AccessCodeInput: React.FC<{
35
34
  }
36
35
 
37
36
  return (
38
- <div className={cn('flex flex-col gap-2 mx-auto w-full text-center items-center', className)}>
37
+ <Box className={cn('flex flex-col gap-2 mx-auto w-full text-center items-center', className)}>
39
38
  <InputOTP
40
39
  className='mx-auto'
41
40
  maxLength={6}
@@ -64,7 +63,7 @@ const AccessCodeInput: React.FC<{
64
63
  null
65
64
  }
66
65
  </p>
67
- </div>
66
+ </Box>
68
67
  )
69
68
  }
70
69
 
@@ -1,23 +1,14 @@
1
1
  'use client'
2
2
 
3
- import { useEffect } from 'react'
4
3
  import { GoogleAnalytics } from '@next/third-parties/google'
5
- import { FacebookPixel } from '../next/analytics'
6
-
7
- const Analytics = () => {
8
4
 
9
- useEffect(() => {
10
- document.body.style.display = 'flex'
11
- }, [])
5
+ import { FacebookPixel } from '../next/analytics'
12
6
 
13
- return (
14
- <>
15
- <FacebookPixel />
16
- <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID ?? ''} />
17
- </>
18
- )
19
- }
7
+ const Analytics = () => (
8
+ <>
9
+ <FacebookPixel />
10
+ <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID ?? ''} />
11
+ </>
12
+ )
20
13
 
21
- export {
22
- Analytics as default
23
- }
14
+ export { Analytics as default }
@@ -0,0 +1,40 @@
1
+ 'use client'
2
+
3
+ import React, { useEffect, useState } from 'react'
4
+
5
+ import { useIam } from '@hanzo/iam/react'
6
+
7
+ import { resume } from './resume'
8
+
9
+ /**
10
+ * The site's OAuth redirect target. IAM sends the authorization code here and
11
+ * this redeems it for tokens with the PKCE verifier the browser kept, then
12
+ * returns the visitor to the page they left.
13
+ */
14
+ const Callback = () => {
15
+
16
+ const { handleCallback } = useIam()
17
+ const [error, setError] = useState<string | null>(null)
18
+
19
+ useEffect(() => {
20
+ handleCallback()
21
+ .then(() => { window.location.replace(resume()) })
22
+ .catch((e: Error) => { setError(e.message) })
23
+ /* Runs once: the authorization code is single-use. */
24
+ }, [])
25
+
26
+ return (
27
+ <main className='flex min-h-screen items-center justify-center p-8 text-center'>
28
+ {error ? (
29
+ <div className='flex flex-col gap-4'>
30
+ <p role='alert'>{error}</p>
31
+ <a href='/' className='underline'>Back to the site</a>
32
+ </div>
33
+ ) : (
34
+ <p>Signing you in…</p>
35
+ )}
36
+ </main>
37
+ )
38
+ }
39
+
40
+ export default Callback
@@ -1,58 +1,28 @@
1
1
  'use client'
2
2
 
3
+ import React from 'react'
3
4
  import Link from 'next/link'
4
- import { useRouter } from 'next/navigation'
5
- import { setCookie } from 'cookies-next'
6
5
 
7
- import { cn } from '@hanzo/ui/util'
6
+ import { Box, cn } from '@hanzo/ui'
8
7
  import { Button, Carousel, CarouselContent, CarouselItem } from '@hanzo/ui/primitives'
9
- import { LoginPanel as Login } from '@hanzo/auth/components'
10
8
 
11
- import { LuxLogo } from '../icons'
9
+ import Login from './login'
10
+ import { LuxLogo } from '@luxfi/logo'
12
11
  import Logo from '../logo'
13
- import { EmblaAutoplay } from '..'
14
12
  import { legal } from '../../site-def/footer'
15
13
 
16
14
  const LoginPanel: React.FC<{
17
15
  close: () => void
18
- getStartedUrl?: string
19
- redirectUrl?: string
20
16
  className?: string
21
17
  reviews: { text: string, author: string, href: string }[]
22
- setIsLogin?: React.Dispatch<React.SetStateAction<boolean>>
23
18
  }> = ({
24
19
  close,
25
- getStartedUrl = '/',
26
- redirectUrl,
27
20
  className = '',
28
- reviews,
29
- setIsLogin
21
+ reviews
30
22
  }) => {
31
- const router = useRouter()
32
-
33
- const termsOfServiceUrl = legal.find(({ title }) => title === 'Terms and Conditions')?.href || ''
34
- const privacyPolicyUrl = legal.find(({ title }) => title === 'Privacy Policy')?.href || ''
35
- const domains = ['lux.id', 'lux.credit', 'lux.market', 'lux.network', 'lux.shop', 'wallet.lux.network', 'safe.lux.network', 'lux.finance', 'lux.exchange', 'lux.quest']
36
- // TODO :aa shouldn't this happen in @hanzo/auth: components/LoginPanel ??
37
- // Otherwise, the functionality is split across modules! (and client/fw!)
38
- // (This was never my intent w the onLoginChanged callback.)
39
- const onLogin = (token: string) => {
40
- for (let i = 0; i < domains.length; i ++) {
41
- setCookie('auth-token', token, {
42
- domain: domains[i],
43
- path: '/',
44
- sameSite: 'none',
45
- secure: true,
46
- httpOnly: false,
47
- expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30), // 30 days
48
- })
49
- }
50
-
51
- redirectUrl && router.push(redirectUrl)
52
- }
53
23
 
54
24
  return (
55
- <div className={cn('grid grid-cols-1 md:grid-cols-2', className)}>
25
+ <Box className={cn('grid grid-cols-1 md:grid-cols-2', className)}>
56
26
  <div className='hidden md:flex w-full h-full bg-[radial-gradient(circle_at_24.1%_68.8%_,_rgba(15,14,14,0)_,_rgba(9,9,9,99.4))] flex-row items-end justify-end overflow-y-auto min-h-screen'>
57
27
  <div className='h-full w-full max-w-[750px] px-8 pt-0'>
58
28
  <div className='h-full w-full max-w-[550px] mx-auto flex flex-col justify-between min-h-screen py-10'>
@@ -61,12 +31,14 @@ const LoginPanel: React.FC<{
61
31
  onClick={close}
62
32
  className='w-fit !min-w-0 p-2'
63
33
  >
64
- <Logo size='md' textClx='!cursor-pointer' variant='text-only' />
34
+ <Logo size='md' variant='wordmark' />
65
35
  </Button>
66
36
  <Carousel
67
- options={{ align: 'center', loop: true }}
37
+ // Autoplay is an option now, not a plugin — and it holds while the
38
+ // pointer is over the carousel or focus is inside it, which is
39
+ // what `stopOnInteraction` was asking for.
40
+ options={{ align: 'center', loop: true, autoplay: 5000 }}
68
41
  className='w-full'
69
- plugins={[EmblaAutoplay({ delay: 5000, stopOnInteraction: true })]}
70
42
  >
71
43
  <CarouselContent>
72
44
  {reviews.map(({ text, author, href }, index) => (
@@ -90,21 +62,21 @@ const LoginPanel: React.FC<{
90
62
  onClick={close}
91
63
  className='block md:hidden absolute rounded-full p-2 left-0 h-auto hover:bg-background'
92
64
  >
93
- <LuxLogo className='w-5 h-5' />
65
+ <LuxLogo variant='white' size={20} />
94
66
  </Button>
95
- <Login
96
- getStartedUrl={getStartedUrl}
97
- redirectUrl={redirectUrl}
98
- className='w-full max-w-sm'
99
- termsOfServiceUrl={termsOfServiceUrl}
100
- privacyPolicyUrl={privacyPolicyUrl}
101
- onLoginChanged={onLogin}
102
- setIsLogin={setIsLogin}
103
- />
67
+ <Login className='w-full max-w-sm' />
68
+ <p className='text-sm opacity-50'>
69
+ {legal.map(({ title, href }, index) => (
70
+ <React.Fragment key={href}>
71
+ {index > 0 && ' · '}
72
+ <Link href={href} className='underline'>{title}</Link>
73
+ </React.Fragment>
74
+ ))}
75
+ </p>
104
76
  </div>
105
77
  </div>
106
78
  </div>
107
- </div>
79
+ </Box>
108
80
  )
109
81
  }
110
82
 
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ import React, { useEffect, useState } from 'react'
4
+
5
+ import { cn } from '@hanzo/ui'
6
+ import { Login as IamLogin, useIam } from '@hanzo/iam/react'
7
+
8
+ import { remember } from './resume'
9
+
10
+ /**
11
+ * Sign-in rendered in place, on this site's own domain: IAM mints an
12
+ * authorization code bound to a PKCE challenge and the form navigates to
13
+ * /auth/callback to redeem it. No credential ever reaches this bundle's
14
+ * origin, and there is one sign-in surface rather than one per app.
15
+ */
16
+ const Login: React.FC<{
17
+ className?: string
18
+ onSuccess?: (token: string) => void
19
+ }> = ({
20
+ className,
21
+ onSuccess
22
+ }) => {
23
+
24
+ const { config } = useIam()
25
+ /* OIDC anti-CSRF nonce, one per mounted form. */
26
+ const [state] = useState(() => crypto.randomUUID())
27
+
28
+ /* Sign-in leaves for /auth/callback, so record the page holding this form to
29
+ come back to. */
30
+ useEffect(remember, [])
31
+
32
+ return (
33
+ <IamLogin
34
+ serverUrl={config.serverUrl}
35
+ clientId={config.clientId}
36
+ redirectUri={config.redirectUri}
37
+ organization={config.organization}
38
+ state={state}
39
+ /* Naming a class replaces the view's own, so keep both: the site skins
40
+ .hanzo-iam-* once, in lux-global.css. */
41
+ className={cn('hanzo-iam-card', className)}
42
+ onSuccess={onSuccess}
43
+ />
44
+ )
45
+ }
46
+
47
+ export default Login
@@ -0,0 +1,34 @@
1
+ 'use client'
2
+
3
+ import React, { type PropsWithChildren } from 'react'
4
+
5
+ import { IamProvider } from '@hanzo/iam/react'
6
+ import { BRAND_SERVER_URLS } from '@hanzo/iam/paths'
7
+ import type { IAMConfig } from '@hanzo/iam/browser'
8
+
9
+ /* These are Lux sites, so they sign in at Lux's own issuer. Naming another
10
+ brand's would send a Lux user to a login page that has never heard of the
11
+ redirect_uri they came from. */
12
+ const ORG = 'lux'
13
+
14
+ /* Where the authorization code comes back. Every site mounts <Callback/> here. */
15
+ const CALLBACK = '/auth/callback'
16
+
17
+ /* The OAuth client a site presents itself as: the `<org>-<app>` pair registered
18
+ on lux.id, so lux.shop is `lux-shop`. */
19
+ const configure = (app: string): IAMConfig => ({
20
+ serverUrl: BRAND_SERVER_URLS[ORG],
21
+ organization: ORG,
22
+ clientId: `${ORG}-${app}`,
23
+ /* Read per render rather than at module load: one bundle serves the real
24
+ host and any preview of it, and each must redeem its code back to itself. */
25
+ redirectUri: (typeof window === 'undefined' ? '' : window.location.origin) + CALLBACK,
26
+ })
27
+
28
+ /* Provider is memoized on the config's values, not its identity, so building a
29
+ fresh object here does not rebuild the SDK. */
30
+ const Auth: React.FC<{ app: string } & PropsWithChildren> = ({ app, children }) => (
31
+ <IamProvider config={configure(app)}>{children}</IamProvider>
32
+ )
33
+
34
+ export default Auth
@@ -0,0 +1,16 @@
1
+ /* Where the visitor was when they chose to sign in. The OIDC round-trip lands
2
+ on /auth/callback, so without this every sign-in would end at the home page.
3
+ Session-scoped: it describes one trip through the browser, not a preference. */
4
+ const KEY = 'lux-auth-resume'
5
+
6
+ const remember = () => {
7
+ sessionStorage.setItem(KEY, window.location.pathname + window.location.search)
8
+ }
9
+
10
+ const resume = (): string => {
11
+ const to = sessionStorage.getItem(KEY)
12
+ sessionStorage.removeItem(KEY)
13
+ return to ?? '/'
14
+ }
15
+
16
+ export { remember, resume }
@@ -0,0 +1,80 @@
1
+ 'use client'
2
+
3
+ import React, { useContext } from 'react'
4
+
5
+ import { Button, Popover, PopoverContent, PopoverTrigger, Separator } from '@hanzo/ui/primitives'
6
+ import { cn } from '@hanzo/ui'
7
+ import { IamContext, useIam, useIamIdentity } from '@hanzo/iam/react'
8
+
9
+ import { remember } from './resume'
10
+
11
+ interface Props {
12
+ /** Hide the sign-in button, leaving only the profile of an already signed-in
13
+ user. Checkout wants this: it has its own sign-in step. */
14
+ noLogin?: boolean
15
+ className?: string
16
+ /** Sign in in place instead of leaving for lux.id — the mobile drawer opens
17
+ its own panel. */
18
+ handleLogin?: () => void
19
+ }
20
+
21
+ const Widget: React.FC<Props> = ({
22
+ noLogin = false,
23
+ handleLogin,
24
+ className
25
+ }) => {
26
+
27
+ const { isAuthenticated, isLoading, login, logout } = useIam()
28
+ const identity = useIamIdentity()
29
+
30
+ /* Say nothing until the stored token has been read, rather than flashing
31
+ "Login" at someone who is already signed in. */
32
+ if (isLoading) {
33
+ return null
34
+ }
35
+
36
+ if (!isAuthenticated) {
37
+ return noLogin ? null : (
38
+ <Button
39
+ variant='primary'
40
+ className={cn('h-8 w-fit !min-w-0', className)}
41
+ onClick={handleLogin ?? (() => { remember(); void login() })}
42
+ >
43
+ Login
44
+ </Button>
45
+ )
46
+ }
47
+
48
+ return (
49
+ <Popover>
50
+ <PopoverTrigger asChild>
51
+ <Button
52
+ variant='outline'
53
+ size='icon'
54
+ className={cn('rounded-full text-muted border-2 border-muted bg-level-1 hover:bg-level-2 hover:border-foreground hover:text-foreground uppercase w-8 h-8', className)}
55
+ >{identity?.initials}</Button>
56
+ </PopoverTrigger>
57
+ <PopoverContent className='bg-level-0'>
58
+ <div className='grid gap-4'>
59
+ <div className='space-y-2 truncate'>
60
+ <h4 className='font-medium leading-none truncate'>{identity?.name}</h4>
61
+ {identity?.email && identity.email !== identity.name && (
62
+ <p className='text-sm opacity-50 truncate'>{identity.email}</p>
63
+ )}
64
+ </div>
65
+ <Separator />
66
+ <Button variant='outline' onClick={() => { logout() }}>Logout</Button>
67
+ </div>
68
+ </PopoverContent>
69
+ </Popover>
70
+ )
71
+ }
72
+
73
+ /* The widget rides in shared chrome, and not every site mounts <Auth> —
74
+ app/credit carries its own root layout. A site that offers no sign-in shows
75
+ no sign-in, which is why the context is read before any hook that needs it. */
76
+ const AuthWidget: React.FC<Props> = (props) => (
77
+ useContext(IamContext) ? <Widget {...props} /> : null
78
+ )
79
+
80
+ export default AuthWidget
@@ -4,17 +4,14 @@ import { useRouter } from 'next/navigation'
4
4
 
5
5
  import { ChevronLeft } from 'lucide-react'
6
6
 
7
-
8
7
  import {
9
8
  Button,
10
9
  buttonVariants,
11
10
  } from '@hanzo/ui/primitives'
12
-
13
- import type { VariantProps } from '@hanzo/ui/util'
14
-
11
+ import { css, type ButtonSize, type ButtonVariant } from '@hanzo/ui'
15
12
  const BackButton: React.FC<{
16
- variant?: VariantProps<typeof buttonVariants>['variant']
17
- size?: VariantProps<typeof buttonVariants>['size']
13
+ variant?: { variant?: ButtonVariant | null; size?: ButtonSize | null }['variant']
14
+ size?: { variant?: ButtonVariant | null; size?: ButtonSize | null }['size']
18
15
  onBack?: () => void
19
16
  clx?: string
20
17
  iconClx?: string
@@ -41,7 +38,7 @@ const BackButton: React.FC<{
41
38
  onClick={back}
42
39
  className={clx}
43
40
  >
44
- <ChevronLeft className={iconClx}/>
41
+ <ChevronLeft style={css(iconClx)}/>
45
42
  </Button>
46
43
  )
47
44
  }
@@ -2,9 +2,8 @@
2
2
  import React from 'react'
3
3
 
4
4
  import { Button, Card } from '@hanzo/ui/primitives'
5
- import { cn } from '@hanzo/ui/util'
6
-
7
- import { LuxLogo } from './icons'
5
+ import { Box, cn } from '@hanzo/ui'
6
+ import { LuxLogo } from '@luxfi/logo'
8
7
  import type { ChatbotSuggestedQuestion } from '../types'
9
8
 
10
9
  const ChatWidget: React.FC<{
@@ -49,7 +48,7 @@ const ChatWidget: React.FC<{
49
48
  const iframeSrc = `${chatbotUrl}?${searchParams.toString()}`
50
49
 
51
50
  return (<>
52
- <div className={
51
+ <Box className={
53
52
  'fixed bottom-0 sm:bottom-16 right-0 w-full h-full ' +
54
53
  'sm:max-w-[400px] sm:max-h-[550px] sm:px-4 z-floating ' +
55
54
  (showChatbot ? 'flex' : 'hidden')
@@ -58,16 +57,14 @@ const ChatWidget: React.FC<{
58
57
  <div className='flex px-4 py-2 h-12 bg-level-0 items-center justify-between'>
59
58
  <h1 className='font-semibold font-heading'>{title} <span className='opacity-60'>{subtitle}</span></h1>
60
59
  <Button onClick={onClick} variant='link' size='icon' className='w-fit sm:hidden'>
61
- <LuxLogo width={24} height={24}/>
60
+ <LuxLogo variant='white' size={24}/>
62
61
  </Button>
63
62
  </div>
64
63
  <iframe src={iframeSrc} className='h-full' />
65
64
  </Card>
66
- </div>
65
+ </Box>
67
66
 
68
- <LuxLogo
69
- width={28}
70
- height={28}
67
+ <Box tag="span"
71
68
  onClick={onClick}
72
69
  className={cn(
73
70
  // z-index should be below anything in commerce-iu (buy drawer and checkout widget)
@@ -77,8 +74,9 @@ const ChatWidget: React.FC<{
77
74
  showChatbot ? 'rotate-180' : '',
78
75
  buttonClx
79
76
  )}
80
- strokeWidth={1}
81
- />
77
+ >
78
+ <LuxLogo variant='white' size={28} />
79
+ </Box>
82
80
  </>)
83
81
  }
84
82
 
@@ -4,7 +4,7 @@ import { observable, type IObservableValue, reaction, runInAction } from 'mobx'
4
4
  import { observer } from 'mobx-react-lite'
5
5
 
6
6
  import { buttonVariants } from '@hanzo/ui/primitives'
7
- import { cn, type VariantProps } from '@hanzo/ui/util'
7
+ import { Box, cn, type ButtonSize, type ButtonVariant } from '@hanzo/ui'
8
8
  import { useCommerce } from '@hanzo/commerce'
9
9
 
10
10
  import { Bag } from '../icons'
@@ -13,7 +13,7 @@ const BagButton: React.FC<{
13
13
  showIfEmpty?: boolean
14
14
  animateOnHover?: boolean
15
15
  animateOnQuantityChange?: boolean
16
- size?: VariantProps<typeof buttonVariants>['size']
16
+ size?: { variant?: ButtonVariant | null; size?: ButtonSize | null }['size']
17
17
  className?: string
18
18
  iconClx?: string
19
19
  onClick?: () => void
@@ -57,12 +57,13 @@ const BagButton: React.FC<{
57
57
 
58
58
 
59
59
  return (
60
- <div
60
+ <Box
61
61
  aria-label="Bag"
62
62
  role='button'
63
63
  onClick={onClick}
64
64
  className={cn(
65
- buttonVariants({ variant: 'ghost', size, rounded: 'md' }),
65
+ buttonVariants({ variant: 'ghost', size }),
66
+ 'rounded-md',
66
67
  // Overides the bg change on hover --not a "hover effect"
67
68
  'relative group p-0 aspect-square hover:bg-background',
68
69
  ((wiggleRef.current.get() === 'more') ?
@@ -73,14 +74,14 @@ const BagButton: React.FC<{
73
74
  )}
74
75
  >
75
76
  {!c.cartEmpty && (
76
- <div className={
77
+ <Box className={
77
78
  'z-above-content flex flex-col justify-center items-center ' +
78
79
  'absolute left-0 right-0 top-0 bottom-0 ' +
79
80
  'leading-none font-sans font-bold text-primary-fg text-xs '
80
81
  }>
81
82
  <div className='h-[3px] w-full' />
82
83
  <div>{c.cartQuantity}</div>
83
- </div>
84
+ </Box>
84
85
  )}
85
86
  <Bag width='24' height='28' className={cn(
86
87
  'relative -top-[3px] fill-primary',
@@ -91,7 +92,7 @@ const BagButton: React.FC<{
91
92
  ''
92
93
  )
93
94
  )} aria-hidden="true" />
94
- </div>
95
+ </Box>
95
96
  )
96
97
  })
97
98
 
@@ -2,14 +2,13 @@
2
2
  import React, {type PropsWithChildren} from 'react'
3
3
 
4
4
  import { Button, buttonVariants } from '@hanzo/ui/primitives'
5
- import { type VariantProps } from '@hanzo/ui/util'
6
5
 
7
- import { cn } from '@hanzo/ui/util'
6
+ import { cn, type ButtonSize, type ButtonVariant } from '@hanzo/ui'
8
7
  import { useSelectAndBuy } from '../../commerce/ui/context'
9
8
 
10
9
  const BuyButton: React.FC<
11
10
  PropsWithChildren &
12
- VariantProps<typeof buttonVariants> &
11
+ { variant?: ButtonVariant | null; size?: ButtonSize | null } &
13
12
  {
14
13
  skuPath: string
15
14
  className?: string