From db8d895f2a951f7d6c18f47896906f3b7204b88a Mon Sep 17 00:00:00 2001 From: Artem Ash <633467+artemis-prime@users.noreply.github.com> Date: Sun, 28 Apr 2024 01:28:17 -0700 Subject: [PATCH] Support for Buy Drawer and Checkout Widget improvements in client app (#98) ui@3.6.4, auth@2.4.5, cmmc@6.4.6 ui: Added "textTransform: 'uppercase'" to Typography plugin for h1,2,3 package.json: added exports section util/TwoWayMap exporting VariantProps from 'class-variance-authority' LinkDef now extends VariantProps primitives/Button now conforms more cleanly w VariantProps primitives/Drawer added 'modal' prop to Content; shouldScaleBackground is now false by default primitives/LinkElement now conforms more cleanly w VariantProps cmmc: context/CommerceUI moved context to a dir at the top level CommerceContextValue now has the Service, and a CommerceUI store new useCommerceUI to access store registers most recently interacted with LineItem controls showing buy UI which is now impl in client app components/buy/CarouselBuyCard CheckoutButton is a render component components/buy/AllVaraiantsCarousel fix: doesn't show swatch only one option util/LineItemRef * pnpm 9.x issue, downgrading to 8.15.7 * moved drawer to client sites mono (packages/core) * bumps: ui@3.7.0, cmmc@7.0.0 --- package.json | 3 +- packages/auth/package.json | 6 +- packages/commerce/components/Icons.tsx | 2 +- .../{buy => }/add-to-cart-widget.tsx | 41 +++- .../_to_deprecate_select-family-item-card.tsx | 109 ---------- ..._to_deprecate_select-family-item-panel.tsx | 192 ------------------ .../commerce/components/buy/buy-button.tsx | 40 ++-- packages/commerce/components/buy/buy-card.tsx | 5 +- .../commerce/components/buy/buy-drawer.tsx | 83 -------- .../components/buy/buy-trigger-wrapper.tsx | 20 -- .../components/buy/carousel-buy-card.tsx | 36 ++-- .../multi-family/all-variants-carousel.tsx | 6 +- .../commerce/components/buy/please-keep.txt | 27 --- .../commerce/components/cart/cart-icon.tsx | 10 - .../cart/cart-panel/cart-line-item.tsx | 2 +- .../payment-step-form/contact-form.tsx | 4 +- packages/commerce/components/index.ts | 12 +- .../components/item-selector/button.tsx | 7 +- .../item-selector/carousel/index.tsx | 2 +- .../quantity-indicator.tsx | 2 +- .../commerce/components/item/product-card.tsx | 4 +- packages/commerce/context/commerce-ui.ts | 86 ++++++-- packages/commerce/context/index.tsx | 24 ++- packages/commerce/index.ts | 3 +- packages/commerce/package.json | 11 +- packages/commerce/tsconfig.json | 2 +- packages/commerce/types/item-selector.ts | 3 + packages/commerce/util/index.ts | 6 +- .../util/item-selector-options-accessor.ts | 3 + packages/commerce/util/line-item-ref.ts | 22 ++ packages/commerce/util/obs-string-mutator.ts | 18 +- packages/tsconfig.hanzo-modules.base.json | 1 + packages/ui/blocks/components/cta-block.tsx | 6 +- packages/ui/package.json | 16 +- packages/ui/primitives/action-button.tsx | 23 ++- packages/ui/primitives/button.tsx | 7 - packages/ui/primitives/drawer.tsx | 50 +++-- packages/ui/primitives/index.ts | 3 +- packages/ui/primitives/link-element.tsx | 27 ++- .../tailwind/tailwind.config.hanzo-preset.js | 5 - packages/ui/types/image-def.ts | 3 +- packages/ui/types/link-def.ts | 8 +- packages/ui/util/index.ts | 5 +- packages/ui/util/two-way-map.ts | 19 ++ pnpm-lock.yaml | 56 ++--- 45 files changed, 349 insertions(+), 671 deletions(-) rename packages/commerce/components/{buy => }/add-to-cart-widget.tsx (79%) delete mode 100644 packages/commerce/components/buy/_to_deprecate_select-family-item-card.tsx delete mode 100644 packages/commerce/components/buy/_to_deprecate_select-family-item-panel.tsx delete mode 100644 packages/commerce/components/buy/buy-drawer.tsx delete mode 100644 packages/commerce/components/buy/buy-trigger-wrapper.tsx delete mode 100644 packages/commerce/components/buy/please-keep.txt delete mode 100644 packages/commerce/components/cart/cart-icon.tsx rename packages/commerce/components/{ => item-selector}/quantity-indicator.tsx (96%) create mode 100644 packages/commerce/util/line-item-ref.ts create mode 100644 packages/ui/util/two-way-map.ts diff --git a/package.json b/package.json index 4931022b..11b272f7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "url": "https://twitter.com/hanzoai" }, "scripts": { - "clean:all": "rm -rf node_modules && pnpm -r clean" + "clean:nm": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +", + "clean:all": "pnpm clean:nm && rm pnpm-lock.yaml" }, "packageManager": "pnpm@8.15.7", "dependencies": { diff --git a/packages/auth/package.json b/packages/auth/package.json index f1a0fa32..656b402e 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -23,9 +23,7 @@ "scripts": { "lat": "npm show @hanzo/auth version", "pub": "npm publish", - "build": "tsc", - "tc": "tsc", - "clean": "rm -rf dist && rm -rf node_modules" + "tc": "tsc" }, "dependencies": { "firebase-admin": "^12.1.0" @@ -39,7 +37,7 @@ "mobx-react-lite": "^4.0.5", "next": "14.1.3", "react": "^18.2.0", - "react-hook-form": "^7.50.1", + "react-hook-form": "^7.51.3", "zod": "3.21.4" }, "devDependencies": { diff --git a/packages/commerce/components/Icons.tsx b/packages/commerce/components/Icons.tsx index 9c5acf99..b5835780 100644 --- a/packages/commerce/components/Icons.tsx +++ b/packages/commerce/components/Icons.tsx @@ -16,7 +16,7 @@ import { Barcode } from "lucide-react" -export const Icons = { +export default { shoppingCart: ShoppingCart, menu: Menu, chevronLeft: ChevronLeft, diff --git a/packages/commerce/components/buy/add-to-cart-widget.tsx b/packages/commerce/components/add-to-cart-widget.tsx similarity index 79% rename from packages/commerce/components/buy/add-to-cart-widget.tsx rename to packages/commerce/components/add-to-cart-widget.tsx index 6b6088b0..89dc2678 100644 --- a/packages/commerce/components/buy/add-to-cart-widget.tsx +++ b/packages/commerce/components/add-to-cart-widget.tsx @@ -1,16 +1,19 @@ 'use client' -import React from 'react' +import React, { useEffect, useRef } from 'react' +import { reaction, type IReactionDisposer } from 'mobx' import { observer } from 'mobx-react-lite' -import { Button, toast, type ButtonSizes, type ButtonVariants } from '@hanzo/ui/primitives' -import { cn } from '@hanzo/ui/util' +import { Button, buttonVariants } from '@hanzo/ui/primitives' +import { cn, type VariantProps } from '@hanzo/ui/util' -import { Icons } from '../Icons' -import type { LineItem } from '../../types' -import { sendFBEvent, sendGAEvent } from '../../util/analytics' +import Icons from './Icons' +import type { LineItem } from '../types' +import { sendFBEvent, sendGAEvent } from '../util/analytics' +import { useCommerceUI } from '..' const AddToCartWidget: React.FC<{ item: LineItem + registerAdd?: boolean disabled?: boolean className?: string buttonClx?: string @@ -19,12 +22,36 @@ const AddToCartWidget: React.FC<{ }> = observer(({ item, variant='primary', + registerAdd=true, disabled=false, className='', buttonClx='', onQuantityChanged }) => { + const ui = useCommerceUI() + + const reactionDisposer = useRef(undefined) + + useEffect(() => { + // Only tell the micro-drawer + // if we're not part of the cart ui, + // or part of the main drawer. + if (registerAdd && variant !== 'minimal') { + reactionDisposer.current = reaction( + () => (item.quantity), + (quantity: number, previous: number) => { + ui.itemQuantityChanged(item, quantity, previous) + } + ) + } + return () => { + if (reactionDisposer.current) { + reactionDisposer.current() + } + } + }, []) + const ROUNDED_VAL = 'lg' // no need to safelist, since its used widely const ROUNDED_CLX = ` rounded-${ROUNDED_VAL} ` @@ -149,7 +176,7 @@ const AddToCartWidget: React.FC<{ } - /> -) + ...rest +}) => { + const ui = useCommerceUI() + const handleClick = () => { ui.showBuyOptions(skuPath) } + + return ( + + ) +} export default BuyButton diff --git a/packages/commerce/components/buy/buy-card.tsx b/packages/commerce/components/buy/buy-card.tsx index e4c0a583..3be1f4a5 100644 --- a/packages/commerce/components/buy/buy-card.tsx +++ b/packages/commerce/components/buy/buy-card.tsx @@ -19,7 +19,7 @@ import * as pathUtils from '../../service/path-utils' import { getFacetValuesMutator, ObsStringMutator } from '../../util' import NodeTabs from '../node-tabs' -import AddToCartWidget from './add-to-cart-widget' +import AddToCartWidget from '../add-to-cart-widget' const BuyCard: React.FC<{ skuPath: string @@ -215,7 +215,7 @@ const BuyCard: React.FC<{ mobile={mobile} mutator={allVariants ? { - get: () => (inst.current!.currentFamTokenMutator.s), + get: () => (inst.current!.currentFamTokenMutator.get()), set: setFamilyPath } : @@ -248,6 +248,7 @@ const BuyCard: React.FC<{ {(cmmc.currentItem) && ( diff --git a/packages/commerce/components/buy/buy-drawer.tsx b/packages/commerce/components/buy/buy-drawer.tsx deleted file mode 100644 index e747a8bc..00000000 --- a/packages/commerce/components/buy/buy-drawer.tsx +++ /dev/null @@ -1,83 +0,0 @@ -'use client' -import React, { useState, type ReactNode } from 'react' -import { useRouter } from 'next/navigation' - -import { X as LucideX} from 'lucide-react' - -import { - Drawer, - DrawerTrigger, - DrawerContent, - Button, -} from '@hanzo/ui/primitives' - -import { cn } from '@hanzo/ui/util' - -//import BuyCard from './buy-card' - -import CarouselBuyCard from './carousel-buy-card' - -const BuyDrawer: React.FC<{ - skuPath: string - trigger: ReactNode - triggerClx?: string - drawerClx?: string - cardClx?: string - mobile?: boolean -}> = ({ - skuPath, - trigger, - triggerClx='', - drawerClx='', - cardClx='', - mobile=false -}) => { - - const [open, setOpen] = useState(false) - const router = useRouter() - - return ( - - - {trigger} - - - {router.push('/checkout')}} - mobile={mobile} - clx={cn('w-full', cardClx)} - /> - - - - - - ) -} - -export default BuyDrawer - -/* - -*/ \ No newline at end of file diff --git a/packages/commerce/components/buy/buy-trigger-wrapper.tsx b/packages/commerce/components/buy/buy-trigger-wrapper.tsx deleted file mode 100644 index c29a062d..00000000 --- a/packages/commerce/components/buy/buy-trigger-wrapper.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client' -import React from 'react' - -import BuyDrawer from './buy-drawer' - -const BuyTriggerWrapper: React.FC<{ - skuPath: string - trigger: React.ReactNode - mobileTrigger?: React.ReactNode - mobile?: boolean -}> = ({ - skuPath, - trigger, - mobileTrigger, - mobile -}) => ( - -) - -export default BuyTriggerWrapper diff --git a/packages/commerce/components/buy/carousel-buy-card.tsx b/packages/commerce/components/buy/carousel-buy-card.tsx index 59836ea0..7f701343 100644 --- a/packages/commerce/components/buy/carousel-buy-card.tsx +++ b/packages/commerce/components/buy/carousel-buy-card.tsx @@ -8,7 +8,6 @@ import React, { import { observer } from 'mobx-react-lite' import { cn } from '@hanzo/ui/util' -import { Button } from '@hanzo/ui/primitives' import type { ItemSelectorProps, @@ -29,7 +28,7 @@ import { CarouselItemSelector, ButtonItemSelector } from '../item-selector' import SingleFamilySelector from './single-family-selector' import { FamilyCarousel, AllVariantsCarousel } from './multi-family' -import AddToCartWidget from './add-to-cart-widget' +import AddToCartWidget from '../add-to-cart-widget' const SCROLL = { scrollAfter: 5, @@ -52,15 +51,19 @@ const sortItems = (items: LineItem[], sort: 'asc' | 'desc' | 'none'): LineItem[] const CarouselBuyCard: React.FC<{ skuPath: string + checkoutButton: React.ReactNode clx?: string + selectorClx?: string + addBtnClx?: string mobile?: boolean - handleCheckout: () => void onQuantityChanged?: (sku: string, oldV: number, newV: number) => void }> = ({ skuPath, + checkoutButton, clx='', + selectorClx='', + addBtnClx='', mobile=false, - handleCheckout, onQuantityChanged, }) => { @@ -92,6 +95,12 @@ const CarouselBuyCard: React.FC<{ useEffect(() => { + if (!skuPath || skuPath.length === 0 ) { + // The component is being hidden (w an amination) + // keep things the same so no layout jump + return + } + const peek = cmmc.peek(skuPath) if (typeof peek === 'string') { throw new Error(peek) @@ -194,26 +203,18 @@ const CarouselBuyCard: React.FC<{
- {!cmmc.cartEmpty && ( - - )} + {!cmmc.cartEmpty && checkoutButton}
) : null)) return (
@@ -222,13 +223,14 @@ const CarouselBuyCard: React.FC<{ {...r.current.single} mediaConstraint={MEDIA_CONSTRAINT} mobile={mobile} + clx={selectorClx} /> ) : (r.current?.multi && r.current.families && /* safegaurd for first render, etc. */ ( ))} { @@ -208,8 +208,8 @@ const AllVariantsCarousel: React.FC = ({ const { showItemSwatches } = accessMultiSelectorOptions(selectorOptions) if ( !showItemSwatches || - !cmmc.currentFamily || - cmmc.currentFamily.products.length === 1 + !cmmc.currentFamily + //|| cmmc.currentFamily.products.length === 1 ) { return null } diff --git a/packages/commerce/components/buy/please-keep.txt b/packages/commerce/components/buy/please-keep.txt deleted file mode 100644 index 63829116..00000000 --- a/packages/commerce/components/buy/please-keep.txt +++ /dev/null @@ -1,27 +0,0 @@ - {/* - (inst.current!.currentFamily.s), - set: setFamilyPath - } - : - getFacetValuesMutator(inst.current.level + 1, cmmc) - } - itemClx='flex-col h-auto gap-0 py-1 px-3' - buttonClx={ - 'h-full ' + - '!border-level-3' - } - levelNodes={inst.current.parentNode.subNodes!} - show={familyTabAs} - /> - */} - )} diff --git a/packages/commerce/components/cart/cart-icon.tsx b/packages/commerce/components/cart/cart-icon.tsx deleted file mode 100644 index 4f4c09ce..00000000 --- a/packages/commerce/components/cart/cart-icon.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { type LucideProps } from 'lucide-react' - -const CartIcon: React.FC = (props: LucideProps) => ( - - - -) - -export default CartIcon diff --git a/packages/commerce/components/cart/cart-panel/cart-line-item.tsx b/packages/commerce/components/cart/cart-panel/cart-line-item.tsx index c17b8877..69cb91c5 100644 --- a/packages/commerce/components/cart/cart-panel/cart-line-item.tsx +++ b/packages/commerce/components/cart/cart-panel/cart-line-item.tsx @@ -8,7 +8,7 @@ import { Image } from '@hanzo/ui/primitives' import type { LineItem } from '../../../types' import { formatCurrencyValue } from '../../../util' -import AddToCartWidget from '../../buy/add-to-cart-widget' +import AddToCartWidget from '../../add-to-cart-widget' import { useCommerce } from '../../../context' const DEF_IMG_SIZE=40 diff --git a/packages/commerce/components/checkout/payment-step-form/contact-form.tsx b/packages/commerce/components/checkout/payment-step-form/contact-form.tsx index b4ae60ba..51de9750 100644 --- a/packages/commerce/components/checkout/payment-step-form/contact-form.tsx +++ b/packages/commerce/components/checkout/payment-step-form/contact-form.tsx @@ -4,8 +4,8 @@ import { FormField, FormItem, FormMessage, -} from '@hanzo/ui/primitives/form' -import { Input } from '@hanzo/ui/primitives' + Input +} from '@hanzo/ui/primitives' import type { ContactFormType } from '../../../types' diff --git a/packages/commerce/components/index.ts b/packages/commerce/components/index.ts index 81a3fa42..dcf73961 100644 --- a/packages/commerce/components/index.ts +++ b/packages/commerce/components/index.ts @@ -1,17 +1,13 @@ -export { default as AddToCartWidget } from './buy/add-to-cart-widget' -export { default as BuyTriggerWrapper } from './buy/buy-trigger-wrapper' +export { default as AddToCartWidget } from './add-to-cart-widget' export { default as BuyButton } from './buy/buy-button' -export { default as BuyCard } from './buy/buy-card' -export { default as BuyDrawer } from './buy/buy-drawer' +export { default as CarouselBuyCard } from './buy/carousel-buy-card' export { default as CartAccordian } from './cart/cart-accordian' export { default as CartPanel } from './cart/cart-panel' -export { default as NodeTabs } from './node-tabs' +export { default as Icons } from './Icons' export { default as PaymentStepForm } from './checkout/payment-step-form' -export { default as ShippingStepForm } from './checkout/shipping-step-form' - export { default as ProductCard } from './item/product-card' -export { Icons } from './Icons' +export { default as ShippingStepForm } from './checkout/shipping-step-form' export * from './item-selector' diff --git a/packages/commerce/components/item-selector/button.tsx b/packages/commerce/components/item-selector/button.tsx index 2481de2e..f03c33d0 100644 --- a/packages/commerce/components/item-selector/button.tsx +++ b/packages/commerce/components/item-selector/button.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useEffect, useRef } from 'react' +import React from 'react' import { observer } from 'mobx-react-lite' import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" @@ -16,7 +16,7 @@ import type { Dimensions } from '@hanzo/ui/types' import type { ItemSelectorProps, LineItem } from '../../types' import { accessItemOptions, formatCurrencyValue } from '../../util' -import QuantityIndicator from '../quantity-indicator' +import QuantityIndicator from './quantity-indicator' const DEFAULT_CONSTRAINT = {h: 36, w: 72} // // Apple suggest 42px for clickability @@ -78,6 +78,7 @@ const ButtonItemSelector: React.FC = observer(({ showFamilyInOption, buttonType, horizButtons, + showButtonIfOnlyOne } = accessItemOptions(options) const showImage = buttonType !== 'text' @@ -148,7 +149,7 @@ const ButtonItemSelector: React.FC = observer(({ ) }) - return items.length > 1 ? ( + return showButtonIfOnlyOne || items.length > 1 ? ( = (props: LucideProps) => ( diff --git a/packages/commerce/components/item/product-card.tsx b/packages/commerce/components/item/product-card.tsx index 2f2a357a..955b44f8 100644 --- a/packages/commerce/components/item/product-card.tsx +++ b/packages/commerce/components/item/product-card.tsx @@ -14,7 +14,7 @@ import { cn } from '@hanzo/ui/util' import { formatCurrencyValue } from '../../util' import type { LineItem } from '../../types' -import AddToCartWidget from '../buy/add-to-cart-widget' +import AddToCartWidget from '../add-to-cart-widget' interface ProductCardProps extends React.HTMLAttributes { item: LineItem @@ -39,7 +39,7 @@ const ProductCard: React.FC = ({ - + ) diff --git a/packages/commerce/context/commerce-ui.ts b/packages/commerce/context/commerce-ui.ts index fb599ed7..07e4a4f9 100644 --- a/packages/commerce/context/commerce-ui.ts +++ b/packages/commerce/context/commerce-ui.ts @@ -4,54 +4,96 @@ import { makeObservable, observable, } from 'mobx' +import type { CommerceService, LineItem } from '../types' + + interface CommerceUI { showBuyOptions: (skuPath: string) => void hideBuyOptions: () => void + get buyOptionsSkuPath(): string | undefined - get buyOptionsShowing(): boolean - - get skuPath(): string | undefined - clearSkuPath: () => void + itemQuantityChanged(item: LineItem, val: number, prevVal: number): void + get activeItem(): LineItem | undefined } class CommerceUIStore implements CommerceUI { - _skuPath: string | undefined = undefined - _optionsShowing: boolean = false + static readonly TIMEOUT = 2500 + _buyOptionsSkuPath: string | undefined = undefined + _paused: boolean = false + _activeItem: LineItem | undefined = undefined + _lastActivity: number | undefined = undefined + _service: CommerceService - constructor() { + constructor(s: CommerceService) { + this._service = s makeObservable(this, { - _skuPath: observable, - _optionsShowing: observable, + _buyOptionsSkuPath: observable, + _activeItem: observable.shallow, showBuyOptions: action, hideBuyOptions: action, - buyOptionsShowing: computed, - skuPath: computed, - clearSkuPath: action + buyOptionsSkuPath: computed, + itemQuantityChanged: action, + tick: action, + activeItem: computed }) } showBuyOptions = (skuPath: string): void => { - this._skuPath = skuPath - this._optionsShowing = true + this._service.setCurrentItem(undefined) + this._buyOptionsSkuPath = skuPath + this._paused = true } hideBuyOptions = (): void => { - this._optionsShowing = false + this._buyOptionsSkuPath = undefined + this._paused = false + if (this._lastActivity) { + this._lastActivity = Date.now() + } } - get buyOptionsShowing(): boolean { - return this._optionsShowing + get buyOptionsSkuPath(): string | undefined { + return this._buyOptionsSkuPath } - get skuPath(): string | undefined { - return this._skuPath + tick = () => { + if ( + !this._paused + && + this._lastActivity + && + (Date.now() - this._lastActivity >= CommerceUIStore.TIMEOUT) + ) { + this._activeItem = undefined + this._lastActivity = undefined + } + } + + itemQuantityChanged = (item: LineItem, val: number, oldVal: number, ): void => { + + if (val === 0) { + if (this._activeItem?.sku === item.sku) { + this._activeItem = undefined + this._lastActivity = undefined + } + // otherwise ignore + } + else if (val < oldVal) { + if (this._activeItem?.sku === item.sku) { + this._lastActivity = Date.now() + } + // otherwise ignore + } + else { + this._activeItem = item + this._lastActivity = Date.now() + } } - clearSkuPath = (): void => { - this._skuPath = undefined - this._optionsShowing = false + get activeItem(): LineItem | undefined { + return this._activeItem } } diff --git a/packages/commerce/context/index.tsx b/packages/commerce/context/index.tsx index 404a958d..c6e7dc14 100644 --- a/packages/commerce/context/index.tsx +++ b/packages/commerce/context/index.tsx @@ -3,7 +3,8 @@ import React, { createContext, useContext, useRef, - type PropsWithChildren + type PropsWithChildren, + useEffect } from 'react' // https://dev.to/ivandotv/mobx-server-side-rendering-with-next-js-4m18 @@ -44,13 +45,21 @@ const CommerceProvider: React.FC { + useEffect(() => { + const intervalId = setInterval(() => { + (valueRef.current.ui as CommerceUIStore).tick() + }, 250) + return () => {clearInterval(intervalId)} + }, []) + // TODO: Inject Promo fixture here from siteDef - const serviceRef = useRef({ - service: getInstance(families, rootNode, options, uiSpecs), - ui: new CommerceUIStore() - }) + const service = getInstance(families, rootNode, options, uiSpecs) + const ui = new CommerceUIStore(service) + + const valueRef = useRef({service, ui}) + return ( - + {children} ) @@ -60,4 +69,5 @@ export { useCommerce, useCommerceUI, CommerceProvider -} \ No newline at end of file +} + diff --git a/packages/commerce/index.ts b/packages/commerce/index.ts index 6d2bc4c5..77a5e7e4 100644 --- a/packages/commerce/index.ts +++ b/packages/commerce/index.ts @@ -5,7 +5,8 @@ export { useSyncSkuParamWithCurrentItem, getFacetValuesMutator, formatCurrencyValue, - ProductMediaAccessor + ProductMediaAccessor, + LineItemRef } from './util' export * from './util/selection-ui-specifiers' \ No newline at end of file diff --git a/packages/commerce/package.json b/packages/commerce/package.json index 2cebd488..4e604e28 100644 --- a/packages/commerce/package.json +++ b/packages/commerce/package.json @@ -1,8 +1,7 @@ { "name": "@hanzo/commerce", - "version": "6.4.7", + "version": "7.0.0", "description": "e-commerce framework.", - "type": "module", "publishConfig": { "registry": "https://registry.npmjs.org/", "access": "public", @@ -23,9 +22,7 @@ "scripts": { "lat": "npm show @hanzo/commerce version", "pub": "npm publish", - "build": "tsc", - "tc": "tsc", - "clean": "rm -rf dist && rm -rf node_modules" + "tc": "tsc" }, "exports": { ".": "./index.ts", @@ -41,7 +38,7 @@ }, "peerDependencies": { "@hanzo/auth": "^2.4.5", - "@hanzo/ui": "^3.6.4", + "@hanzo/ui": "^3.7.0", "@hookform/resolvers": "^3.3.4", "@radix-ui/react-radio-group": "^1.1.3", "firebase": "^10.8.0", @@ -52,7 +49,7 @@ "next": "14.1.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hook-form": "7.50.1", + "react-hook-form": "^7.51.3", "zod": "3.21.4" }, "devDependencies": { diff --git a/packages/commerce/tsconfig.json b/packages/commerce/tsconfig.json index 7d99cf13..14208f3e 100644 --- a/packages/commerce/tsconfig.json +++ b/packages/commerce/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.hanzo-modules.base.json", "include": [ "**/*.ts", - "**/*.tsx", + "**/*.tsx", "types/string-mutator.ts", ], "exclude": [ "node_modules", diff --git a/packages/commerce/types/item-selector.ts b/packages/commerce/types/item-selector.ts index 4484a5f8..d674575e 100644 --- a/packages/commerce/types/item-selector.ts +++ b/packages/commerce/types/item-selector.ts @@ -55,6 +55,9 @@ type ItemSelectorOptions = { * default: false */ showSlider?: boolean + /** default true */ + showButtonIfOnlyOne?: boolean + /** * Sort by cost. * If it's a carousel selector and 'showSlider' is true, diff --git a/packages/commerce/util/index.ts b/packages/commerce/util/index.ts index dcb7fd11..3f19641c 100644 --- a/packages/commerce/util/index.ts +++ b/packages/commerce/util/index.ts @@ -28,7 +28,6 @@ export function formatCurrencyValue(price: number): string { return (str.endsWith('.00')) ? str.replace('.00', '') : str } - export const getFacetValuesMutator = (level: number, cmmc: CommerceService): StringMutator => { const setLevel = (value: string, level: number ): void => { @@ -57,7 +56,6 @@ export const getFacetValuesMutator = (level: number, cmmc: CommerceService): Str } satisfies StringMutator } - export { default as useSyncSkuParamWithCurrentItem } from './use-sync-sku-param-w-current-item' export { default as processSquareCardPayment } from './square-payment' export { default as ObsStringMutator } from './obs-string-mutator' @@ -68,4 +66,6 @@ export * from './selection-ui-specifiers' export { getErrorMessage } from './error' export { default as accessItemOptions } from './item-selector-options-accessor' -export { default as accessMultiSelectorOptions } from './multi-family-selector-options-accessor' \ No newline at end of file +export { default as accessMultiSelectorOptions } from './multi-family-selector-options-accessor' + +export { default as LineItemRef} from './line-item-ref' \ No newline at end of file diff --git a/packages/commerce/util/item-selector-options-accessor.ts b/packages/commerce/util/item-selector-options-accessor.ts index 612d0bbe..32fce3cf 100644 --- a/packages/commerce/util/item-selector-options-accessor.ts +++ b/packages/commerce/util/item-selector-options-accessor.ts @@ -15,6 +15,8 @@ export default (options: ItemSelectorOptions | undefined = {}): Required { this._item = v } +} + +export default LineItemRef diff --git a/packages/commerce/util/obs-string-mutator.ts b/packages/commerce/util/obs-string-mutator.ts index 9a6d023a..3cea9f01 100644 --- a/packages/commerce/util/obs-string-mutator.ts +++ b/packages/commerce/util/obs-string-mutator.ts @@ -1,22 +1,22 @@ +import {makeObservable, observable, action} from 'mobx' -import { makeObservable, observable, action } from 'mobx' import type { StringMutator } from '../types' class ObsStringMutator implements StringMutator { - s: string | null - constructor(_s: string) { - this.s = _s + _s: string | null + + constructor(s: string) { + this._s = s makeObservable(this, { - s: observable, + _s: observable, set: action, - //get: computed + //get: computed .// no need }) } - set(v: string | null): void { this.s = v } - get(): string | null { return this.s } + set(v: string | null): void { this._s = v } + get(): string | null { return this._s } } export default ObsStringMutator - diff --git a/packages/tsconfig.hanzo-modules.base.json b/packages/tsconfig.hanzo-modules.base.json index f1ad2928..05e64668 100644 --- a/packages/tsconfig.hanzo-modules.base.json +++ b/packages/tsconfig.hanzo-modules.base.json @@ -19,3 +19,4 @@ }, "plugins": [{ "name": "next"}], } + \ No newline at end of file diff --git a/packages/ui/blocks/components/cta-block.tsx b/packages/ui/blocks/components/cta-block.tsx index 0467d5de..f9f49c53 100644 --- a/packages/ui/blocks/components/cta-block.tsx +++ b/packages/ui/blocks/components/cta-block.tsx @@ -1,15 +1,15 @@ import React from 'react' import type { LinkDef, ButtonDef} from '../../types' -import { type ButtonSizes, ActionButton, LinkElement } from '../../primitives' +import { buttonVariants, ActionButton, LinkElement } from '../../primitives' import type { CTABlock } from '../def' -import { cn, containsToken } from '../../util' +import { cn, containsToken, type VariantProps } from '../../util' import type BlockComponentProps from './block-component-props' const CtaBlockComponent: React.FC['size'], renderLink?: (def: LinkDef, key: any) => JSX.Element renderButton?: (def: ButtonDef, key: any) => JSX.Element }> = ({ diff --git a/packages/ui/package.json b/packages/ui/package.json index 687932f1..0ac9fa97 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@hanzo/ui", - "version": "3.6.5", + "version": "3.7.0", "description": "Library that contains shared UI primitives, support for a common design system, and other boilerplate support.", "publishConfig": { "registry": "https://registry.npmjs.org/", @@ -31,9 +31,15 @@ "scripts": { "lat": "npm show @hanzo/ui version", "pub": "npm publish", - "build": "tsc", - "tc": "tsc", - "clean": "rm -rf dist && rm -rf node_modules" + "tc": "tsc" + }, + "exports": { + "./blocks": "./blocks/index.ts", + "./primitives": "./primitives/index.ts", + "./style/": "./style/*", + "./tailwind": "./tailwind/index.ts", + "./types": "./types/index.ts", + "./util": "./util/index.ts" }, "dependencies": { "@next/third-parties": "^14.1.0", @@ -86,7 +92,7 @@ "next-themes": "^0.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hook-form": "^7.47.0", + "react-hook-form": "^7.51.3", "validator": "^13.11.0", "zod": "3.21.4" }, diff --git a/packages/ui/primitives/action-button.tsx b/packages/ui/primitives/action-button.tsx index dbff4fdd..14bb2950 100644 --- a/packages/ui/primitives/action-button.tsx +++ b/packages/ui/primitives/action-button.tsx @@ -1,10 +1,10 @@ import React from 'react' import dynamic from 'next/dynamic' -import { cn } from '../util' +import { cn, type VariantProps } from '../util' import type { ButtonDef, ButtonModalDef } from '../types' -import type { ButtonSizes } from './button' +import type { buttonVariants } from './button' // The DVC must be rendered client-side since it accesses the DOM directly. // There is no need for a loading UI since the dialog only opens @@ -12,26 +12,27 @@ import type { ButtonSizes } from './button' // https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading const DynamicDVC = dynamic(() => (import('./dialog-video-controller')), {ssr: false}) -const ActionButton: React.FC<{ - def: ButtonDef - size?: ButtonSizes - className?: string -}> = ({ +const ActionButton: React.FC< + VariantProps & + { + def: ButtonDef + className?: string + } +> = ({ def, - size, // no default. overrides! - className='' + className='', + ...rest }) => { if (def.action.type === 'modal') { const m = def.action.def as ButtonModalDef const Modal = m.Comp - const sizeToSpread = size ? {size} : {} return ( , VariantProps @@ -81,8 +77,5 @@ Button.displayName = "Button" export { Button as default, type ButtonProps, - type ButtonVariants, - type ButtonSizes, - type ButtonRoundedValue, buttonVariants, } diff --git a/packages/ui/primitives/drawer.tsx b/packages/ui/primitives/drawer.tsx index aaeba74d..bed91d2a 100644 --- a/packages/ui/primitives/drawer.tsx +++ b/packages/ui/primitives/drawer.tsx @@ -6,7 +6,7 @@ import { Drawer as DrawerPrimitive } from 'vaul' import { cn } from '../util' const Drawer = ({ - shouldScaleBackground = true, + shouldScaleBackground = false, ...props }: React.ComponentProps) => ( , React.ComponentPropsWithoutRef & { overlayClx?: string + // Redundancy of 'modal' sent to Root, since we have no access to that. + // Hack that avoids forking their code. + modal?: boolean } ->(({ className, children, overlayClx='', ...props }, ref) => ( - - {/* If no or same z index, overlay should precede content */} - - -
- {children} - - -)) +>(({ className, children, overlayClx='', modal=true, ...props }, ref) => { + + return ( + + {/* If no or same z index, overlay should precede content */} + + { if (!modal) { e.preventDefault(); return } }} + onEscapeKeyDown={(e) => { if (!modal) { e.preventDefault(); return } }} + {...props} + > +
+ {children} + + + ) +}) + DrawerContent.displayName = 'DrawerContent' const DrawerHeader = ({ @@ -105,7 +116,10 @@ const DrawerDescription = React.forwardRef< )) DrawerDescription.displayName = DrawerPrimitive.Description.displayName +type DrawerProps = React.ComponentProps + export { + type DrawerProps, Drawer, DrawerPortal, DrawerOverlay, diff --git a/packages/ui/primitives/index.ts b/packages/ui/primitives/index.ts index d4051b6c..7218b8f9 100644 --- a/packages/ui/primitives/index.ts +++ b/packages/ui/primitives/index.ts @@ -24,8 +24,6 @@ export { export { default as Button, type ButtonProps, - type ButtonVariants, - type ButtonSizes, buttonVariants, } from './button' @@ -61,6 +59,7 @@ export { } from './command' export { + type DrawerProps, Drawer, DrawerPortal, DrawerOverlay, diff --git a/packages/ui/primitives/link-element.tsx b/packages/ui/primitives/link-element.tsx index 08308e3a..de464eb2 100644 --- a/packages/ui/primitives/link-element.tsx +++ b/packages/ui/primitives/link-element.tsx @@ -2,24 +2,26 @@ import React, { type PropsWithChildren } from 'react' import Link from 'next/link' import type { LinkDef, Icon } from '../types' -import { buttonVariants, type ButtonSizes, type ButtonVariants } from './button' -import { cn } from '../util' +import { buttonVariants } from './button' +import { cn, type VariantProps } from '../util' /** * If this is rendered directly (and not auto generated in a Block) * and it has any children, title, icon, and iconAfter * are ignore. */ -const LinkElement: React.FC & +{ def: LinkDef, - /** overrides def */ - variant? : ButtonVariants - /** overrides def */ - size?: ButtonSizes - /** To trigger other events in addition to the - * link action itself. (eg, to also close a drawer menu) + + /** + * Use to trigger other events in addition to the + * link action itself. For example, to also close a drawer menu. */ onClick?: () => void + /** overrides def (eg, for title area)*/ icon?: Icon /** overrides def */ @@ -28,9 +30,10 @@ const LinkElement: React.FC = ({ def, // DO NOT provide a default to any of the props that also appear in def! - size, onClick, + size, variant, + rounded, icon, iconAfter, className = '', @@ -42,6 +45,7 @@ const LinkElement: React.FC 0 || onClick) ? '' : 'pointer-events-none'), diff --git a/packages/ui/tailwind/tailwind.config.hanzo-preset.js b/packages/ui/tailwind/tailwind.config.hanzo-preset.js index f47cf259..37991fe6 100644 --- a/packages/ui/tailwind/tailwind.config.hanzo-preset.js +++ b/packages/ui/tailwind/tailwind.config.hanzo-preset.js @@ -107,11 +107,6 @@ export default { }), borderOpacity: ({ theme }) => theme('opacity'), borderRadius: { - /* shadcn's: - lg: `var(--radius)`, - md: `calc(var(--radius) - 2px)`, - sm: "calc(var(--radius) - 4px)", - */ none: '0px', sm: '0.25rem', DEFAULT: '0.5rem', diff --git a/packages/ui/types/image-def.ts b/packages/ui/types/image-def.ts index 4ed7a3bd..01d7dcd9 100644 --- a/packages/ui/types/image-def.ts +++ b/packages/ui/types/image-def.ts @@ -1,4 +1,3 @@ -import type { ButtonRoundedValue } from '../primitives/button' import type Dimensions from './dimensions' /** @@ -25,7 +24,7 @@ interface ImageDef { */ dim: Dimensions - rounded?: ButtonRoundedValue + rounded?: string // any key from tailwind.config.borderRadius } export { diff --git a/packages/ui/types/link-def.ts b/packages/ui/types/link-def.ts index 28cc8629..5d0c7e20 100644 --- a/packages/ui/types/link-def.ts +++ b/packages/ui/types/link-def.ts @@ -1,4 +1,5 @@ -import type { ButtonVariants, ButtonSizes } from '../primitives/button' +import type { VariantProps } from 'class-variance-authority' +import type { buttonVariants } from '../primitives/button' import type Icon from './icon' /** @@ -6,7 +7,7 @@ import type Icon from './icon' * * */ -interface LinkDef { +interface LinkDef extends VariantProps { /** * If the LinkElement is rendered directly and has children, * the title, icon, iconAfter fields in the supplied LinkDef @@ -49,9 +50,6 @@ interface LinkDef { * rendered as a disabled link, shows default cursor, and eats pointer events. */ disabled?: boolean - - variant?: ButtonVariants - size?: ButtonSizes } export { diff --git a/packages/ui/util/index.ts b/packages/ui/util/index.ts index e1274e65..5051c351 100644 --- a/packages/ui/util/index.ts +++ b/packages/ui/util/index.ts @@ -2,6 +2,8 @@ import { compiler as mdCompiler } from 'markdown-to-jsx' import { clsx, type ClassValue } from 'clsx' import { twMerge } from 'tailwind-merge' +export { cva, type VariantProps } from 'class-variance-authority' + import type { Dimensions } from '../types' // @ts-ignore @@ -71,4 +73,5 @@ export const capitalize = (str: string): string => ( str.charAt(0).toUpperCase() + str.slice(1) ) -export { default as spreadToTransform } from './spread-to-transform' \ No newline at end of file +export { default as spreadToTransform } from './spread-to-transform' + diff --git a/packages/ui/util/two-way-map.ts b/packages/ui/util/two-way-map.ts new file mode 100644 index 00000000..3c4697da --- /dev/null +++ b/packages/ui/util/two-way-map.ts @@ -0,0 +1,19 @@ +class TwoWayReadonlyMap { + map: Map; + reverseMap: Map; + constructor(map: Map) { + this.map = map; + this.reverseMap = new Map(); + map.forEach((value, key) => { + this.reverseMap.set(value, key); + }); + } + get(key: T) { + return this.map.get(key); + } + revGet(key: K) { + return this.reverseMap.get(key); + } +} + +export default TwoWayReadonlyMap diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2ce9c0a..442dcfb8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,7 +193,7 @@ importers: version: 0.4.1(react@18.2.0) recharts: specifier: ^2.6.2 - version: 2.12.6(react-dom@18.2.0)(react@18.2.0) + version: 2.12.5(react-dom@18.2.0)(react@18.2.0) sharp: specifier: ^0.31.3 version: 0.31.3 @@ -214,7 +214,7 @@ importers: version: 0.9.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) zod: specifier: ^3.21.4 - version: 3.23.0 + version: 3.21.4 devDependencies: '@shikijs/compat': specifier: ^1.1.7 @@ -316,7 +316,7 @@ importers: specifier: ^18.2.0 version: 18.2.0 react-hook-form: - specifier: ^7.50.1 + specifier: ^7.51.3 version: 7.51.3(react@18.2.0) zod: specifier: 3.21.4 @@ -338,11 +338,11 @@ importers: specifier: ^2.4.5 version: link:../auth '@hanzo/ui': - specifier: ^3.6.4 + specifier: ^3.7.0 version: link:../ui '@hookform/resolvers': specifier: ^3.3.4 - version: 3.3.4(react-hook-form@7.50.1) + version: 3.3.4(react-hook-form@7.51.3) '@radix-ui/react-radio-group': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) @@ -377,8 +377,8 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) react-hook-form: - specifier: 7.50.1 - version: 7.50.1(react@18.2.0) + specifier: ^7.51.3 + version: 7.51.3(react@18.2.0) react-square-web-payments-sdk: specifier: ^3.2.1 version: 3.2.1(react@18.2.0) @@ -528,7 +528,7 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) react-hook-form: - specifier: ^7.47.0 + specifier: ^7.51.3 version: 7.51.3(react@18.2.0) react-intersection-observer: specifier: ^9.8.2 @@ -1041,7 +1041,7 @@ packages: ts-pattern: 4.3.0 unified: 10.1.2 yaml: 2.4.1 - zod: 3.23.0 + zod: 3.21.4 transitivePeerDependencies: - '@effect-ts/otel-node' - esbuild @@ -1953,14 +1953,6 @@ packages: yargs: 17.7.2 dev: false - /@hookform/resolvers@3.3.4(react-hook-form@7.50.1): - resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==} - peerDependencies: - react-hook-form: ^7.0.0 - dependencies: - react-hook-form: 7.50.1(react@18.2.0) - dev: false - /@hookform/resolvers@3.3.4(react-hook-form@7.51.3): resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==} peerDependencies: @@ -4960,7 +4952,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001612 + caniuse-lite: 1.0.30001611 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -5045,7 +5037,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001612 + caniuse-lite: 1.0.30001611 electron-to-chromium: 1.4.745 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -5102,8 +5094,8 @@ packages: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} dev: false - /caniuse-lite@1.0.30001612: - resolution: {integrity: sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==} + /caniuse-lite@1.0.30001611: + resolution: {integrity: sha512-19NuN1/3PjA3QI8Eki55N8my4LzfkMCRLgCVfrl/slbSAchQfV0+GwjPrK3rq37As4UCLlM/DHajbKkAqbv92Q==} /ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -9044,7 +9036,7 @@ packages: '@next/env': 13.5.6 '@swc/helpers': 0.5.2 busboy: 1.6.0 - caniuse-lite: 1.0.30001612 + caniuse-lite: 1.0.30001611 postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -9084,7 +9076,7 @@ packages: '@opentelemetry/api': 1.8.0 '@swc/helpers': 0.5.2 busboy: 1.6.0 - caniuse-lite: 1.0.30001612 + caniuse-lite: 1.0.30001611 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.2.0 @@ -9566,15 +9558,6 @@ packages: scheduler: 0.23.0 dev: false - /react-hook-form@7.50.1(react@18.2.0): - resolution: {integrity: sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ==} - engines: {node: '>=12.22.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 - dependencies: - react: 18.2.0 - dev: false - /react-hook-form@7.51.3(react@18.2.0): resolution: {integrity: sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ==} engines: {node: '>=12.22.0'} @@ -9762,8 +9745,8 @@ packages: decimal.js-light: 2.5.1 dev: false - /recharts@2.12.6(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-D+7j9WI+D0NHauah3fKHuNNcRK8bOypPW7os1DERinogGBGaHI7i6tQKJ0aUF3JXyBZ63dyfKIW2WTOPJDxJ8w==} + /recharts@2.12.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Cy+BkqrFIYTHJCyKHJEPvbHE2kVQEP6PKbOHJ8ztRGTAhvHuUnCwDaKVb13OwRFZ0QNUk1QvGTDdgWSMbuMtKw==} engines: {node: '>=14'} peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 @@ -11381,7 +11364,6 @@ packages: /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - requiresBuild: true /yoga-layout-prebuilt@1.10.0: resolution: {integrity: sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==} @@ -11398,9 +11380,5 @@ packages: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} dev: false - /zod@3.23.0: - resolution: {integrity: sha512-OFLT+LTocvabn6q76BTwVB0hExEBS0IduTr3cqZyMqEDbOnYmcU+y0tUAYbND4uwclpBGi4I4UUBGzylWpjLGA==} - dev: false - /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}