@shopify/shop-minis-react 0.2.5 → 0.2.7

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 (26) hide show
  1. package/dist/_virtual/index4.js +2 -2
  2. package/dist/_virtual/index5.js +3 -2
  3. package/dist/_virtual/index5.js.map +1 -1
  4. package/dist/_virtual/index6.js +2 -3
  5. package/dist/_virtual/index6.js.map +1 -1
  6. package/dist/components/commerce/product-card.js +61 -58
  7. package/dist/components/commerce/product-card.js.map +1 -1
  8. package/dist/components/commerce/product-link.js +144 -131
  9. package/dist/components/commerce/product-link.js.map +1 -1
  10. package/dist/components/commerce/search.js +15 -15
  11. package/dist/components/commerce/search.js.map +1 -1
  12. package/dist/components/ui/drawer.js +30 -28
  13. package/dist/components/ui/drawer.js.map +1 -1
  14. package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
  15. package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
  16. package/dist/shop-minis-react/node_modules/.pnpm/querystringify@2.2.0/node_modules/querystringify/index.js +1 -1
  17. package/dist/shop-minis-react/node_modules/.pnpm/simple-swizzle@0.2.2/node_modules/simple-swizzle/index.js +1 -1
  18. package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js +1 -1
  19. package/eslint/rules/validate-manifest.cjs +99 -1
  20. package/package.json +1 -1
  21. package/src/components/commerce/product-card.test.tsx +68 -0
  22. package/src/components/commerce/product-card.tsx +10 -2
  23. package/src/components/commerce/product-link.test.tsx +81 -0
  24. package/src/components/commerce/product-link.tsx +36 -10
  25. package/src/components/commerce/search.tsx +1 -1
  26. package/src/components/ui/drawer.tsx +7 -3
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sources":["../../../src/components/commerce/search.tsx"],"sourcesContent":["import * as React from 'react'\nimport {createContext, useContext, useState, useCallback} from 'react'\n\nimport {SearchIcon, X} from 'lucide-react'\n\nimport {useProductSearch} from '../../hooks/product/useProductSearch'\nimport {cn} from '../../lib/utils'\nimport {type Product} from '../../types'\nimport {IconButton} from '../atoms/icon-button'\nimport {List} from '../atoms/list'\nimport {Input} from '../ui/input'\n\nimport {ProductLink} from './product-link'\nimport {ProductLinkSkeleton} from './product-link-skeleton'\n\ninterface SearchContextValue {\n query: string\n setQuery: (query: string) => void\n products: Product[] | null\n loading: boolean\n error: Error | null\n fetchMore?: () => Promise<void>\n hasNextPage: boolean\n isTyping: boolean\n}\n\nconst SearchContext = createContext<SearchContextValue | null>(null)\n\nfunction useSearchContext() {\n const context = useContext(SearchContext)\n if (!context) {\n throw new Error('useSearchContext must be used within a SearchProvider')\n }\n return context\n}\n\nexport interface SearchProviderProps {\n initialQuery?: string\n children: React.ReactNode\n}\n\nfunction SearchProvider({initialQuery = '', children}: SearchProviderProps) {\n const [query, setQueryState] = useState(initialQuery)\n\n const {products, loading, error, fetchMore, hasNextPage, isTyping} =\n useProductSearch({\n query,\n fetchPolicy: 'network-only',\n })\n\n const setQuery = useCallback((newQuery: string) => {\n setQueryState(newQuery)\n }, [])\n\n const contextValue: SearchContextValue = {\n query,\n setQuery,\n products,\n loading,\n error,\n fetchMore,\n hasNextPage,\n isTyping,\n }\n\n return (\n <SearchContext.Provider value={contextValue}>\n {children}\n </SearchContext.Provider>\n )\n}\n\nexport interface SearchInputProps {\n placeholder?: string\n className?: string\n inputProps?: React.ComponentProps<'input'>\n}\n\nfunction SearchInput({\n placeholder = 'Search products...',\n className,\n inputProps,\n}: SearchInputProps) {\n const {query, setQuery} = useSearchContext()\n\n const handleQueryChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n setQuery(event.target.value)\n inputProps?.onChange?.(event)\n },\n [inputProps, setQuery]\n )\n\n return (\n <div className=\"relative flex flex-1 items-center rounded-full pl-4 pr-2 py-1 bg-gray-100\">\n <div className=\"relative flex items-center\">\n <SearchIcon\n size={18}\n className={cn('text-accent-foreground opacity-60')}\n />\n </div>\n <div className=\"relative flex-1 flex items-center mx-2\">\n <Input\n name=\"search\"\n onChange={handleQueryChange}\n placeholder={placeholder}\n type=\"search\"\n role=\"searchbox\"\n autoComplete=\"off\"\n value={query}\n data-testid=\"search-input\"\n {...inputProps}\n className={cn(\n `w-full flex overflow-hidden rounded-radius-28 border-none py-4 px-0 text-text placeholder:text-text placeholder:opacity-60`,\n className\n )}\n />\n </div>\n <div className=\"relative flex items-center\">\n {query === '' ? null : (\n <IconButton\n Icon={X}\n size=\"sm\"\n filled={false}\n iconStyles=\"\"\n onClick={() => setQuery('')}\n buttonStyles=\"flex items-center rounded-radius-max bg-[var(--grayscale-l20)]\"\n />\n )}\n </div>\n </div>\n )\n}\n\nexport interface SearchResultsListProps {\n renderItem?: (product: Product, index: number) => React.ReactNode\n height?: number\n itemHeight?: number\n initialStateComponent?: React.JSX.Element\n showScrollbar?: boolean\n overscanCount?: number\n}\n\nfunction SearchResultsList({\n height = window.innerHeight,\n renderItem,\n initialStateComponent,\n showScrollbar,\n}: SearchResultsListProps) {\n const {query, products, loading, fetchMore, hasNextPage, isTyping} =\n useSearchContext()\n\n const _renderItem = (product: Product, index: number) => {\n if (renderItem) {\n return renderItem(product, index)\n }\n\n return (\n <div className=\"p-2\">\n <ProductLink key={product.id} product={product} hideFavoriteAction />\n </div>\n )\n }\n\n const shouldShowStartingState = query.trim().length === 0\n const shouldShowLoading =\n (!products || products.length === 0) && (loading || isTyping)\n const shouldShowEmptyState = (!products || products.length === 0) && !loading\n\n if (shouldShowStartingState) {\n return (\n initialStateComponent || (\n <div className=\"flex items-center justify-center h-32 text-gray-500\">\n Start typing to search for products\n </div>\n )\n )\n }\n\n if (shouldShowLoading) {\n return (\n <div className=\"flex flex-col px-4 py-4\">\n <ProductLinkSkeleton className=\"mb-4\" />\n <ProductLinkSkeleton className=\"mb-4\" />\n <ProductLinkSkeleton className=\"mb-4\" />\n <ProductLinkSkeleton className=\"mb-4\" />\n </div>\n )\n }\n\n if (shouldShowEmptyState) {\n return (\n <div className=\"flex items-center justify-center h-32 text-gray-500\">\n {`No products found for \"${query}\"`}\n </div>\n )\n }\n\n return (\n <List\n items={products || []}\n height={height}\n renderItem={_renderItem}\n fetchMore={hasNextPage ? fetchMore : undefined}\n showScrollbar={showScrollbar}\n />\n )\n}\n\ninterface SearchProviderPropsWithoutChildren\n extends Omit<SearchProviderProps, 'children'> {}\nexport interface SearchResultsProps\n extends SearchProviderPropsWithoutChildren,\n SearchInputProps,\n SearchResultsListProps {\n showSearchInput?: boolean\n onProductClick?: (product: Product) => void\n}\n\nfunction Search({\n initialQuery,\n placeholder,\n inputProps,\n height,\n className,\n renderItem,\n itemHeight,\n onProductClick,\n}: SearchResultsProps) {\n const _renderItem = (product: Product, index: number) => {\n if (renderItem) {\n return renderItem(product, index)\n }\n\n return (\n <div className=\"p-2\">\n <ProductLink\n key={product.id}\n product={product}\n hideFavoriteAction\n onClick={onProductClick}\n />\n </div>\n )\n }\n\n return (\n <SearchProvider initialQuery={initialQuery}>\n <div className={cn('flex flex-col ', className)}>\n <div className=\"fixed top-0 left-0 right-0 p-4 w-full z-20 bg-background\">\n <SearchInput placeholder={placeholder} inputProps={inputProps} />\n </div>\n <div className=\"h-14\" />\n <SearchResultsList\n height={height}\n renderItem={_renderItem}\n itemHeight={itemHeight}\n showScrollbar\n />\n </div>\n </SearchProvider>\n )\n}\n\nexport {SearchProvider, SearchInput, SearchResultsList, Search}\n"],"names":["SearchContext","createContext","useSearchContext","context","useContext","SearchProvider","initialQuery","children","query","setQueryState","useState","products","loading","error","fetchMore","hasNextPage","isTyping","useProductSearch","setQuery","useCallback","newQuery","contextValue","SearchInput","placeholder","className","inputProps","handleQueryChange","event","jsxs","jsx","SearchIcon","cn","Input","IconButton","X","SearchResultsList","height","renderItem","initialStateComponent","showScrollbar","_renderItem","product","index","ProductLink","shouldShowStartingState","shouldShowLoading","shouldShowEmptyState","ProductLinkSkeleton","List","Search","itemHeight","onProductClick"],"mappings":"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"}
1
+ {"version":3,"file":"search.js","sources":["../../../src/components/commerce/search.tsx"],"sourcesContent":["import * as React from 'react'\nimport {createContext, useContext, useState, useCallback} from 'react'\n\nimport {SearchIcon, X} from 'lucide-react'\n\nimport {useProductSearch} from '../../hooks/product/useProductSearch'\nimport {cn} from '../../lib/utils'\nimport {type Product} from '../../types'\nimport {IconButton} from '../atoms/icon-button'\nimport {List} from '../atoms/list'\nimport {Input} from '../ui/input'\n\nimport {ProductLink} from './product-link'\nimport {ProductLinkSkeleton} from './product-link-skeleton'\n\ninterface SearchContextValue {\n query: string\n setQuery: (query: string) => void\n products: Product[] | null\n loading: boolean\n error: Error | null\n fetchMore?: () => Promise<void>\n hasNextPage: boolean\n isTyping: boolean\n}\n\nconst SearchContext = createContext<SearchContextValue | null>(null)\n\nfunction useSearchContext() {\n const context = useContext(SearchContext)\n if (!context) {\n throw new Error('useSearchContext must be used within a SearchProvider')\n }\n return context\n}\n\nexport interface SearchProviderProps {\n initialQuery?: string\n children: React.ReactNode\n}\n\nfunction SearchProvider({initialQuery = '', children}: SearchProviderProps) {\n const [query, setQueryState] = useState(initialQuery)\n\n const {products, loading, error, fetchMore, hasNextPage, isTyping} =\n useProductSearch({\n query,\n fetchPolicy: 'network-only',\n })\n\n const setQuery = useCallback((newQuery: string) => {\n setQueryState(newQuery)\n }, [])\n\n const contextValue: SearchContextValue = {\n query,\n setQuery,\n products,\n loading,\n error,\n fetchMore,\n hasNextPage,\n isTyping,\n }\n\n return (\n <SearchContext.Provider value={contextValue}>\n {children}\n </SearchContext.Provider>\n )\n}\n\nexport interface SearchInputProps {\n placeholder?: string\n className?: string\n inputProps?: React.ComponentProps<'input'>\n}\n\nfunction SearchInput({\n placeholder = 'Search products...',\n className,\n inputProps,\n}: SearchInputProps) {\n const {query, setQuery} = useSearchContext()\n\n const handleQueryChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n setQuery(event.target.value)\n inputProps?.onChange?.(event)\n },\n [inputProps, setQuery]\n )\n\n return (\n <div className=\"relative flex flex-1 items-center rounded-full pl-4 pr-2 py-1 bg-gray-100\">\n <div className=\"relative flex items-center\">\n <SearchIcon\n size={18}\n className={cn('text-accent-foreground opacity-60')}\n />\n </div>\n <div className=\"relative flex-1 flex items-center mx-2\">\n <Input\n name=\"search\"\n onChange={handleQueryChange}\n placeholder={placeholder}\n type=\"search\"\n role=\"searchbox\"\n autoComplete=\"off\"\n value={query}\n data-testid=\"search-input\"\n {...inputProps}\n className={cn(\n `w-full flex overflow-hidden rounded-radius-28 border-none shadow-none py-4 px-0 text-text placeholder:text-text placeholder:opacity-60`,\n className\n )}\n />\n </div>\n <div className=\"relative flex items-center\">\n {query === '' ? null : (\n <IconButton\n Icon={X}\n size=\"sm\"\n filled={false}\n iconStyles=\"\"\n onClick={() => setQuery('')}\n buttonStyles=\"flex items-center rounded-radius-max bg-[var(--grayscale-l20)]\"\n />\n )}\n </div>\n </div>\n )\n}\n\nexport interface SearchResultsListProps {\n renderItem?: (product: Product, index: number) => React.ReactNode\n height?: number\n itemHeight?: number\n initialStateComponent?: React.JSX.Element\n showScrollbar?: boolean\n overscanCount?: number\n}\n\nfunction SearchResultsList({\n height = window.innerHeight,\n renderItem,\n initialStateComponent,\n showScrollbar,\n}: SearchResultsListProps) {\n const {query, products, loading, fetchMore, hasNextPage, isTyping} =\n useSearchContext()\n\n const _renderItem = (product: Product, index: number) => {\n if (renderItem) {\n return renderItem(product, index)\n }\n\n return (\n <div className=\"p-2\">\n <ProductLink key={product.id} product={product} hideFavoriteAction />\n </div>\n )\n }\n\n const shouldShowStartingState = query.trim().length === 0\n const shouldShowLoading =\n (!products || products.length === 0) && (loading || isTyping)\n const shouldShowEmptyState = (!products || products.length === 0) && !loading\n\n if (shouldShowStartingState) {\n return (\n initialStateComponent || (\n <div className=\"flex items-center justify-center h-32 text-gray-500\">\n Start typing to search for products\n </div>\n )\n )\n }\n\n if (shouldShowLoading) {\n return (\n <div className=\"flex flex-col px-4 py-4\">\n <ProductLinkSkeleton className=\"mb-4\" />\n <ProductLinkSkeleton className=\"mb-4\" />\n <ProductLinkSkeleton className=\"mb-4\" />\n <ProductLinkSkeleton className=\"mb-4\" />\n </div>\n )\n }\n\n if (shouldShowEmptyState) {\n return (\n <div className=\"flex items-center justify-center h-32 text-gray-500\">\n {`No products found for \"${query}\"`}\n </div>\n )\n }\n\n return (\n <List\n items={products || []}\n height={height}\n renderItem={_renderItem}\n fetchMore={hasNextPage ? fetchMore : undefined}\n showScrollbar={showScrollbar}\n />\n )\n}\n\ninterface SearchProviderPropsWithoutChildren\n extends Omit<SearchProviderProps, 'children'> {}\nexport interface SearchResultsProps\n extends SearchProviderPropsWithoutChildren,\n SearchInputProps,\n SearchResultsListProps {\n showSearchInput?: boolean\n onProductClick?: (product: Product) => void\n}\n\nfunction Search({\n initialQuery,\n placeholder,\n inputProps,\n height,\n className,\n renderItem,\n itemHeight,\n onProductClick,\n}: SearchResultsProps) {\n const _renderItem = (product: Product, index: number) => {\n if (renderItem) {\n return renderItem(product, index)\n }\n\n return (\n <div className=\"p-2\">\n <ProductLink\n key={product.id}\n product={product}\n hideFavoriteAction\n onClick={onProductClick}\n />\n </div>\n )\n }\n\n return (\n <SearchProvider initialQuery={initialQuery}>\n <div className={cn('flex flex-col ', className)}>\n <div className=\"fixed top-0 left-0 right-0 p-4 w-full z-20 bg-background\">\n <SearchInput placeholder={placeholder} inputProps={inputProps} />\n </div>\n <div className=\"h-14\" />\n <SearchResultsList\n height={height}\n renderItem={_renderItem}\n itemHeight={itemHeight}\n showScrollbar\n />\n </div>\n </SearchProvider>\n )\n}\n\nexport {SearchProvider, SearchInput, SearchResultsList, Search}\n"],"names":["SearchContext","createContext","useSearchContext","context","useContext","SearchProvider","initialQuery","children","query","setQueryState","useState","products","loading","error","fetchMore","hasNextPage","isTyping","useProductSearch","setQuery","useCallback","newQuery","contextValue","SearchInput","placeholder","className","inputProps","handleQueryChange","event","jsxs","jsx","SearchIcon","cn","Input","IconButton","X","SearchResultsList","height","renderItem","initialStateComponent","showScrollbar","_renderItem","product","index","ProductLink","shouldShowStartingState","shouldShowLoading","shouldShowEmptyState","ProductLinkSkeleton","List","Search","itemHeight","onProductClick"],"mappings":"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"}
@@ -1,25 +1,25 @@
1
1
  import { jsx as a, jsxs as o } from "react/jsx-runtime";
2
2
  import { Drawer as e } from "../../shop-minis-react/node_modules/.pnpm/vaul@1.1.2_@types_react-dom@19.1.6_@types_react@19.1.6__@types_react@19.1.6_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/vaul/dist/index.js";
3
3
  import { cn as d } from "../../lib/utils.js";
4
- function s({ ...t }) {
4
+ function m({ ...t }) {
5
5
  return /* @__PURE__ */ a(e.Root, { "data-slot": "drawer", ...t });
6
6
  }
7
- function m({
7
+ function f({
8
8
  ...t
9
9
  }) {
10
10
  return /* @__PURE__ */ a(e.Trigger, { "data-slot": "drawer-trigger", ...t });
11
11
  }
12
- function n({
12
+ function l({
13
13
  ...t
14
14
  }) {
15
15
  return /* @__PURE__ */ a(e.Portal, { "data-slot": "drawer-portal", ...t });
16
16
  }
17
- function f({
17
+ function v({
18
18
  ...t
19
19
  }) {
20
20
  return /* @__PURE__ */ a(e.Close, { "data-slot": "drawer-close", ...t });
21
21
  }
22
- function l({
22
+ function c({
23
23
  className: t,
24
24
  ...r
25
25
  }) {
@@ -35,38 +35,40 @@ function l({
35
35
  }
36
36
  );
37
37
  }
38
- function v({
38
+ function p({
39
39
  className: t,
40
- children: r,
41
- ...i
40
+ fullHeight: r = !1,
41
+ children: i,
42
+ ...n
42
43
  }) {
43
44
  return (
44
45
  // vaul's Portal type incorrectly excludes children
45
- /* @__PURE__ */ o(n, { children: [
46
- /* @__PURE__ */ a(l, {}),
46
+ /* @__PURE__ */ o(l, { children: [
47
+ /* @__PURE__ */ a(c, {}),
47
48
  /* @__PURE__ */ o(
48
49
  e.Content,
49
50
  {
50
51
  "data-slot": "drawer-content",
51
52
  className: d(
52
53
  "group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
53
- "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
54
- "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
54
+ "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[100vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
55
+ "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[100vh] data-[vaul-drawer-direction=bottom]:rounded-t-[28px] data-[vaul-drawer-direction=bottom]:border-t",
55
56
  "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
56
57
  "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
58
+ r ? "h-[100vh]" : "",
57
59
  t
58
60
  ),
59
- ...i,
61
+ ...n,
60
62
  children: [
61
63
  /* @__PURE__ */ a("div", { className: "bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" }),
62
- r
64
+ i
63
65
  ]
64
66
  }
65
67
  )
66
68
  ] })
67
69
  );
68
70
  }
69
- function p({ className: t, ...r }) {
71
+ function g({ className: t, ...r }) {
70
72
  return /* @__PURE__ */ a(
71
73
  "div",
72
74
  {
@@ -79,7 +81,7 @@ function p({ className: t, ...r }) {
79
81
  }
80
82
  );
81
83
  }
82
- function g({ className: t, ...r }) {
84
+ function x({ className: t, ...r }) {
83
85
  return /* @__PURE__ */ a(
84
86
  "div",
85
87
  {
@@ -89,7 +91,7 @@ function g({ className: t, ...r }) {
89
91
  }
90
92
  );
91
93
  }
92
- function x({
94
+ function b({
93
95
  className: t,
94
96
  ...r
95
97
  }) {
@@ -102,7 +104,7 @@ function x({
102
104
  }
103
105
  );
104
106
  }
105
- function b({
107
+ function h({
106
108
  className: t,
107
109
  ...r
108
110
  }) {
@@ -116,15 +118,15 @@ function b({
116
118
  );
117
119
  }
118
120
  export {
119
- s as Drawer,
120
- f as DrawerClose,
121
- v as DrawerContent,
122
- b as DrawerDescription,
123
- g as DrawerFooter,
124
- p as DrawerHeader,
125
- l as DrawerOverlay,
126
- n as DrawerPortal,
127
- x as DrawerTitle,
128
- m as DrawerTrigger
121
+ m as Drawer,
122
+ v as DrawerClose,
123
+ p as DrawerContent,
124
+ h as DrawerDescription,
125
+ x as DrawerFooter,
126
+ g as DrawerHeader,
127
+ c as DrawerOverlay,
128
+ l as DrawerPortal,
129
+ b as DrawerTitle,
130
+ f as DrawerTrigger
129
131
  };
130
132
  //# sourceMappingURL=drawer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"drawer.js","sources":["../../../src/components/ui/drawer.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport {Drawer as DrawerPrimitive} from 'vaul'\n\nimport {cn} from '../../lib/utils'\n\nfunction Drawer({...props}: React.ComponentProps<typeof DrawerPrimitive.Root>) {\n return <DrawerPrimitive.Root data-slot=\"drawer\" {...props} />\n}\n\nfunction DrawerTrigger({\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {\n return <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />\n}\n\nfunction DrawerPortal({\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {\n return <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />\n}\n\nfunction DrawerClose({\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Close>) {\n return <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />\n}\n\nfunction DrawerOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {\n return (\n <DrawerPrimitive.Overlay\n data-slot=\"drawer-overlay\"\n className={cn(\n 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DrawerContent({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Content>) {\n return (\n // vaul's Portal type incorrectly excludes children\n <DrawerPortal {...({} as any)}>\n <DrawerOverlay />\n <DrawerPrimitive.Content\n data-slot=\"drawer-content\"\n className={cn(\n 'group/drawer-content bg-background fixed z-50 flex h-auto flex-col',\n 'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b',\n 'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t',\n 'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm',\n 'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm',\n className\n )}\n {...props}\n >\n <div className=\"bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block\" />\n {children}\n </DrawerPrimitive.Content>\n </DrawerPortal>\n )\n}\n\nfunction DrawerHeader({className, ...props}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"drawer-header\"\n className={cn(\n 'flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DrawerFooter({className, ...props}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"drawer-footer\"\n className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n {...props}\n />\n )\n}\n\nfunction DrawerTitle({\n className,\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Title>) {\n return (\n <DrawerPrimitive.Title\n data-slot=\"drawer-title\"\n className={cn('text-foreground font-semibold', className)}\n {...props}\n />\n )\n}\n\nfunction DrawerDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Description>) {\n return (\n <DrawerPrimitive.Description\n data-slot=\"drawer-description\"\n className={cn('text-muted-foreground text-sm', className)}\n {...props}\n />\n )\n}\n\nexport {\n Drawer,\n DrawerPortal,\n DrawerOverlay,\n DrawerTrigger,\n DrawerClose,\n DrawerContent,\n DrawerHeader,\n DrawerFooter,\n DrawerTitle,\n DrawerDescription,\n}\n"],"names":["Drawer","props","DrawerPrimitive","DrawerTrigger","DrawerPortal","DrawerClose","DrawerOverlay","className","jsx","cn","DrawerContent","children","jsxs","DrawerHeader","DrawerFooter","DrawerTitle","DrawerDescription"],"mappings":";;;AAMA,SAASA,EAAO,EAAC,GAAGC,KAA2D;AAC7E,2BAAQC,EAAgB,MAAhB,EAAqB,aAAU,UAAU,GAAGD,GAAO;AAC7D;AAEA,SAASE,EAAc;AAAA,EACrB,GAAGF;AACL,GAAyD;AACvD,2BAAQC,EAAgB,SAAhB,EAAwB,aAAU,kBAAkB,GAAGD,GAAO;AACxE;AAEA,SAASG,EAAa;AAAA,EACpB,GAAGH;AACL,GAAwD;AACtD,2BAAQC,EAAgB,QAAhB,EAAuB,aAAU,iBAAiB,GAAGD,GAAO;AACtE;AAEA,SAASI,EAAY;AAAA,EACnB,GAAGJ;AACL,GAAuD;AACrD,2BAAQC,EAAgB,OAAhB,EAAsB,aAAU,gBAAgB,GAAGD,GAAO;AACpE;AAEA,SAASK,EAAc;AAAA,EACrB,WAAAC;AAAA,EACA,GAAGN;AACL,GAAyD;AAErD,SAAA,gBAAAO;AAAA,IAACN,EAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAWO;AAAA,QACT;AAAA,QACAF;AAAA,MACF;AAAA,MACC,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASS,EAAc;AAAA,EACrB,WAAAH;AAAA,EACA,UAAAI;AAAA,EACA,GAAGV;AACL,GAAyD;AACvD;AAAA;AAAA,IAEG,gBAAAW,EAAAR,GAAA,EACC,UAAA;AAAA,MAAA,gBAAAI,EAACF,GAAc,EAAA;AAAA,MACf,gBAAAM;AAAA,QAACV,EAAgB;AAAA,QAAhB;AAAA,UACC,aAAU;AAAA,UACV,WAAWO;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACAF;AAAA,UACF;AAAA,UACC,GAAGN;AAAA,UAEJ,UAAA;AAAA,YAAC,gBAAAO,EAAA,OAAA,EAAI,WAAU,kIAAkI,CAAA;AAAA,YAChJG;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,EACF,CAAA;AAAA;AAEJ;AAEA,SAASE,EAAa,EAAC,WAAAN,GAAW,GAAGN,KAAqC;AAEtE,SAAA,gBAAAO;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWC;AAAA,QACT;AAAA,QACAF;AAAA,MACF;AAAA,MACC,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASa,EAAa,EAAC,WAAAP,GAAW,GAAGN,KAAqC;AAEtE,SAAA,gBAAAO;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWC,EAAG,mCAAmCF,CAAS;AAAA,MACzD,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASc,EAAY;AAAA,EACnB,WAAAR;AAAA,EACA,GAAGN;AACL,GAAuD;AAEnD,SAAA,gBAAAO;AAAA,IAACN,EAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAWO,EAAG,iCAAiCF,CAAS;AAAA,MACvD,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASe,EAAkB;AAAA,EACzB,WAAAT;AAAA,EACA,GAAGN;AACL,GAA6D;AAEzD,SAAA,gBAAAO;AAAA,IAACN,EAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAWO,EAAG,iCAAiCF,CAAS;AAAA,MACvD,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;"}
1
+ {"version":3,"file":"drawer.js","sources":["../../../src/components/ui/drawer.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport {Drawer as DrawerPrimitive} from 'vaul'\n\nimport {cn} from '../../lib/utils'\n\nfunction Drawer({...props}: React.ComponentProps<typeof DrawerPrimitive.Root>) {\n return <DrawerPrimitive.Root data-slot=\"drawer\" {...props} />\n}\n\nfunction DrawerTrigger({\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {\n return <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />\n}\n\nfunction DrawerPortal({\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {\n return <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />\n}\n\nfunction DrawerClose({\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Close>) {\n return <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />\n}\n\nfunction DrawerOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {\n return (\n <DrawerPrimitive.Overlay\n data-slot=\"drawer-overlay\"\n className={cn(\n 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DrawerContent({\n className,\n fullHeight = false,\n children,\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Content> & {\n fullHeight?: boolean\n}) {\n return (\n // vaul's Portal type incorrectly excludes children\n <DrawerPortal {...({} as any)}>\n <DrawerOverlay />\n <DrawerPrimitive.Content\n data-slot=\"drawer-content\"\n className={cn(\n 'group/drawer-content bg-background fixed z-50 flex h-auto flex-col',\n 'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[100vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b',\n 'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[100vh] data-[vaul-drawer-direction=bottom]:rounded-t-[28px] data-[vaul-drawer-direction=bottom]:border-t',\n 'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm',\n 'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm',\n fullHeight ? 'h-[100vh]' : '',\n className\n )}\n {...props}\n >\n <div className=\"bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block\" />\n {children}\n </DrawerPrimitive.Content>\n </DrawerPortal>\n )\n}\n\nfunction DrawerHeader({className, ...props}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"drawer-header\"\n className={cn(\n 'flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DrawerFooter({className, ...props}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"drawer-footer\"\n className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n {...props}\n />\n )\n}\n\nfunction DrawerTitle({\n className,\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Title>) {\n return (\n <DrawerPrimitive.Title\n data-slot=\"drawer-title\"\n className={cn('text-foreground font-semibold', className)}\n {...props}\n />\n )\n}\n\nfunction DrawerDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Description>) {\n return (\n <DrawerPrimitive.Description\n data-slot=\"drawer-description\"\n className={cn('text-muted-foreground text-sm', className)}\n {...props}\n />\n )\n}\n\nexport {\n Drawer,\n DrawerPortal,\n DrawerOverlay,\n DrawerTrigger,\n DrawerClose,\n DrawerContent,\n DrawerHeader,\n DrawerFooter,\n DrawerTitle,\n DrawerDescription,\n}\n"],"names":["Drawer","props","DrawerPrimitive","DrawerTrigger","DrawerPortal","DrawerClose","DrawerOverlay","className","jsx","cn","DrawerContent","fullHeight","children","jsxs","DrawerHeader","DrawerFooter","DrawerTitle","DrawerDescription"],"mappings":";;;AAMA,SAASA,EAAO,EAAC,GAAGC,KAA2D;AAC7E,2BAAQC,EAAgB,MAAhB,EAAqB,aAAU,UAAU,GAAGD,GAAO;AAC7D;AAEA,SAASE,EAAc;AAAA,EACrB,GAAGF;AACL,GAAyD;AACvD,2BAAQC,EAAgB,SAAhB,EAAwB,aAAU,kBAAkB,GAAGD,GAAO;AACxE;AAEA,SAASG,EAAa;AAAA,EACpB,GAAGH;AACL,GAAwD;AACtD,2BAAQC,EAAgB,QAAhB,EAAuB,aAAU,iBAAiB,GAAGD,GAAO;AACtE;AAEA,SAASI,EAAY;AAAA,EACnB,GAAGJ;AACL,GAAuD;AACrD,2BAAQC,EAAgB,OAAhB,EAAsB,aAAU,gBAAgB,GAAGD,GAAO;AACpE;AAEA,SAASK,EAAc;AAAA,EACrB,WAAAC;AAAA,EACA,GAAGN;AACL,GAAyD;AAErD,SAAA,gBAAAO;AAAA,IAACN,EAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAWO;AAAA,QACT;AAAA,QACAF;AAAA,MACF;AAAA,MACC,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASS,EAAc;AAAA,EACrB,WAAAH;AAAA,EACA,YAAAI,IAAa;AAAA,EACb,UAAAC;AAAA,EACA,GAAGX;AACL,GAEG;AACD;AAAA;AAAA,IAEG,gBAAAY,EAAAT,GAAA,EACC,UAAA;AAAA,MAAA,gBAAAI,EAACF,GAAc,EAAA;AAAA,MACf,gBAAAO;AAAA,QAACX,EAAgB;AAAA,QAAhB;AAAA,UACC,aAAU;AAAA,UACV,WAAWO;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACAE,IAAa,cAAc;AAAA,YAC3BJ;AAAA,UACF;AAAA,UACC,GAAGN;AAAA,UAEJ,UAAA;AAAA,YAAC,gBAAAO,EAAA,OAAA,EAAI,WAAU,kIAAkI,CAAA;AAAA,YAChJI;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,EACF,CAAA;AAAA;AAEJ;AAEA,SAASE,EAAa,EAAC,WAAAP,GAAW,GAAGN,KAAqC;AAEtE,SAAA,gBAAAO;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWC;AAAA,QACT;AAAA,QACAF;AAAA,MACF;AAAA,MACC,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASc,EAAa,EAAC,WAAAR,GAAW,GAAGN,KAAqC;AAEtE,SAAA,gBAAAO;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWC,EAAG,mCAAmCF,CAAS;AAAA,MACzD,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASe,EAAY;AAAA,EACnB,WAAAT;AAAA,EACA,GAAGN;AACL,GAAuD;AAEnD,SAAA,gBAAAO;AAAA,IAACN,EAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAWO,EAAG,iCAAiCF,CAAS;AAAA,MACvD,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASgB,EAAkB;AAAA,EACzB,WAAAV;AAAA,EACA,GAAGN;AACL,GAA6D;AAEzD,SAAA,gBAAAO;AAAA,IAACN,EAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAWO,EAAG,iCAAiCF,CAAS;AAAA,MACvD,GAAGN;AAAA,IAAA;AAAA,EACN;AAEJ;"}
@@ -1,4 +1,4 @@
1
- import { __module as q } from "../../../../../../../../_virtual/index5.js";
1
+ import { __module as q } from "../../../../../../../../_virtual/index4.js";
2
2
  import { __require as F } from "../../../../../global@4.4.0/node_modules/global/window.js";
3
3
  import { __require as N } from "../../../../../@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/extends.js";
4
4
  import { __require as J } from "../../../../../is-function@1.0.2/node_modules/is-function/index.js";
@@ -2,7 +2,7 @@ import L from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-ut
2
2
  import T from "../../../../../../../_virtual/window.js";
3
3
  import { forEachMediaGroup as Z } from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-utils/es/media-groups.js";
4
4
  import J from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js";
5
- import { l as Q } from "../../../../../../../_virtual/index6.js";
5
+ import { l as Q } from "../../../../../../../_virtual/index5.js";
6
6
  /*! @name mpd-parser @version 1.3.1 @license Apache-2.0 */
7
7
  const w = (e) => !!e && typeof e == "object", E = (...e) => e.reduce((n, t) => (typeof t != "object" || Object.keys(t).forEach((r) => {
8
8
  Array.isArray(n[r]) && Array.isArray(t[r]) ? n[r] = n[r].concat(t[r]) : w(n[r]) && w(t[r]) ? n[r] = E(n[r], t[r]) : n[r] = t[r];
@@ -1,4 +1,4 @@
1
- import { __exports as i } from "../../../../../../_virtual/index4.js";
1
+ import { __exports as i } from "../../../../../../_virtual/index6.js";
2
2
  var c;
3
3
  function d() {
4
4
  if (c) return i;
@@ -1,4 +1,4 @@
1
- import { __module as t } from "../../../../../../_virtual/index11.js";
1
+ import { __module as t } from "../../../../../../_virtual/index10.js";
2
2
  import { __require as z } from "../../../is-arrayish@0.3.2/node_modules/is-arrayish/index.js";
3
3
  var l;
4
4
  function v() {
@@ -1,4 +1,4 @@
1
- import { __module as r } from "../../../../../../../_virtual/index10.js";
1
+ import { __module as r } from "../../../../../../../_virtual/index11.js";
2
2
  import { __require as o } from "../cjs/use-sync-external-store-shim.production.js";
3
3
  import { __require as i } from "../cjs/use-sync-external-store-shim.development.js";
4
4
  var e;
@@ -82,6 +82,10 @@ module.exports = {
82
82
  messages: {
83
83
  missingScope:
84
84
  '{{source}} requires scope "{{scope}}" in src/manifest.json. Add "{{scope}}" to the "scopes" array.',
85
+ missingScopeProductCard:
86
+ 'Component "ProductCard" requires scope "{{scope}}" in src/manifest.json. Add "{{scope}}" to the "scopes" array or set favoriteButtonDisabled to true on all ProductCard instances.',
87
+ missingScopeProductLink:
88
+ 'Component "ProductLink" requires scope "{{scope}}" in src/manifest.json. Add "{{scope}}" to the "scopes" array or set hideFavoriteAction to true (or provide a customAction) on all ProductLink instances.',
85
89
  missingPermission:
86
90
  '{{reason}} requires permission "{{permission}}" in src/manifest.json. Add "{{permission}}" to the "permissions" array.',
87
91
  missingTrustedDomain:
@@ -105,6 +109,8 @@ module.exports = {
105
109
  const requiredPermissions = new Set()
106
110
  const requiredDomains = new Set()
107
111
  const fixedIssues = new Set()
112
+ // Track how components are actually used (e.g., with specific props)
113
+ const componentUsagePatterns = new Map()
108
114
 
109
115
  // Check module-level cache first to avoid repeated file I/O
110
116
  if (manifestPathCache && fs.existsSync(manifestPathCache)) {
@@ -361,6 +367,78 @@ module.exports = {
361
367
  }
362
368
  },
363
369
 
370
+ // Track ProductCard and ProductLink usage with disabled favorite functionality
371
+ JSXElement(node) {
372
+ const elementName = node.openingElement.name.name
373
+
374
+ // Handle ProductCard with favoriteButtonDisabled prop
375
+ if (elementName === 'ProductCard') {
376
+ // Check if favoriteButtonDisabled prop is present and true
377
+ const favoriteDisabledProp = node.openingElement.attributes.find(
378
+ attr =>
379
+ attr.type === 'JSXAttribute' &&
380
+ attr.name?.name === 'favoriteButtonDisabled'
381
+ )
382
+
383
+ const isDisabled =
384
+ favoriteDisabledProp &&
385
+ // Shorthand syntax: <ProductCard favoriteButtonDisabled />
386
+ (favoriteDisabledProp.value === null ||
387
+ // Explicit true: <ProductCard favoriteButtonDisabled={true} />
388
+ (favoriteDisabledProp.value?.type === 'JSXExpressionContainer' &&
389
+ favoriteDisabledProp.value?.expression?.type === 'Literal' &&
390
+ favoriteDisabledProp.value?.expression?.value === true))
391
+
392
+ // Track usage pattern
393
+ const componentPath = 'commerce/product-card'
394
+ if (!componentUsagePatterns.has(componentPath)) {
395
+ componentUsagePatterns.set(componentPath, {
396
+ allDisabled: true,
397
+ hasUsage: true,
398
+ })
399
+ }
400
+ const pattern = componentUsagePatterns.get(componentPath)
401
+ pattern.allDisabled = pattern.allDisabled && isDisabled
402
+ }
403
+
404
+ // Handle ProductLink with hideFavoriteAction or customAction props
405
+ if (elementName === 'ProductLink') {
406
+ // Check if hideFavoriteAction prop is present and true
407
+ const hideFavoriteProp = node.openingElement.attributes.find(
408
+ attr =>
409
+ attr.type === 'JSXAttribute' &&
410
+ attr.name?.name === 'hideFavoriteAction'
411
+ )
412
+
413
+ // Check if customAction prop is present (replaces favorite action)
414
+ const customActionProp = node.openingElement.attributes.find(
415
+ attr =>
416
+ attr.type === 'JSXAttribute' && attr.name?.name === 'customAction'
417
+ )
418
+
419
+ const isFavoriteDisabled =
420
+ // hideFavoriteAction={true} or shorthand
421
+ (hideFavoriteProp &&
422
+ (hideFavoriteProp.value === null || // shorthand
423
+ (hideFavoriteProp.value?.type === 'JSXExpressionContainer' &&
424
+ hideFavoriteProp.value?.expression?.type === 'Literal' &&
425
+ hideFavoriteProp.value?.expression?.value === true))) ||
426
+ // customAction is provided (any truthy value replaces favorites)
427
+ customActionProp !== undefined
428
+
429
+ // Track usage pattern
430
+ const componentPath = 'commerce/product-link'
431
+ if (!componentUsagePatterns.has(componentPath)) {
432
+ componentUsagePatterns.set(componentPath, {
433
+ allDisabled: true,
434
+ hasUsage: true,
435
+ })
436
+ }
437
+ const pattern = componentUsagePatterns.get(componentPath)
438
+ pattern.allDisabled = pattern.allDisabled && isFavoriteDisabled
439
+ }
440
+ },
441
+
364
442
  // Check JSX attributes for external URLs
365
443
  JSXAttribute(node) {
366
444
  if (!node.value || node.value.type !== 'Literal') {
@@ -489,6 +567,18 @@ module.exports = {
489
567
  // Check scopes for components
490
568
  usedComponents.forEach(
491
569
  ({path: componentPath, name: componentName, node}) => {
570
+ // Special handling for components with conditional favorite functionality
571
+ if (
572
+ componentPath === 'commerce/product-card' ||
573
+ componentPath === 'commerce/product-link'
574
+ ) {
575
+ const usagePattern = componentUsagePatterns.get(componentPath)
576
+ // Skip scope requirement if all usages have favorites disabled
577
+ if (usagePattern?.hasUsage && usagePattern?.allDisabled) {
578
+ return // No scope required when favorite functionality is disabled
579
+ }
580
+ }
581
+
492
582
  const componentData = componentScopesMap[componentPath]
493
583
  if (
494
584
  !componentData ||
@@ -597,9 +687,17 @@ module.exports = {
597
687
  const sourceName = issue.hookName || issue.componentName
598
688
  const sourceType = issue.hookName ? 'Hook' : 'Component'
599
689
 
690
+ // Use custom message for ProductCard and ProductLink
691
+ let messageId = 'missingScope'
692
+ if (issue.componentName === 'ProductCard') {
693
+ messageId = 'missingScopeProductCard'
694
+ } else if (issue.componentName === 'ProductLink') {
695
+ messageId = 'missingScopeProductLink'
696
+ }
697
+
600
698
  context.report({
601
699
  loc: {line: 1, column: 0},
602
- messageId: 'missingScope',
700
+ messageId,
603
701
  data: {
604
702
  source: `${sourceType} "${sourceName}"`,
605
703
  scope: issue.scope,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shopify/shop-minis-react",
3
3
  "license": "SEE LICENSE IN LICENSE.txt",
4
- "version": "0.2.5",
4
+ "version": "0.2.7",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {
@@ -147,6 +147,33 @@ describe('ProductCard', () => {
147
147
 
148
148
  expect(screen.getByText('No Image')).toBeInTheDocument()
149
149
  })
150
+
151
+ it('renders favorite button by default', () => {
152
+ const product = mockProduct()
153
+ render(<ProductCard product={product} />)
154
+
155
+ // Favorite button should be present
156
+ const favoriteButton = screen.getByRole('button')
157
+ expect(favoriteButton).toBeInTheDocument()
158
+ })
159
+
160
+ it('hides favorite button when favoriteButtonDisabled is true', () => {
161
+ const product = mockProduct()
162
+ render(<ProductCard product={product} favoriteButtonDisabled />)
163
+
164
+ // Favorite button should not be present
165
+ const favoriteButton = screen.queryByRole('button')
166
+ expect(favoriteButton).not.toBeInTheDocument()
167
+ })
168
+
169
+ it('shows favorite button when favoriteButtonDisabled is false', () => {
170
+ const product = mockProduct()
171
+ render(<ProductCard product={product} favoriteButtonDisabled={false} />)
172
+
173
+ // Favorite button should be present
174
+ const favoriteButton = screen.getByRole('button')
175
+ expect(favoriteButton).toBeInTheDocument()
176
+ })
150
177
  })
151
178
 
152
179
  describe('Interactions', () => {
@@ -262,6 +289,28 @@ describe('ProductCard', () => {
262
289
  expect(favoriteButton).toHaveClass('bg-button-overlay/30')
263
290
  })
264
291
  })
292
+
293
+ it('does not allow favorite toggle when favoriteButtonDisabled is true', async () => {
294
+ const product = mockProduct({isFavorited: false})
295
+ const onFavoriteToggled = vi.fn()
296
+
297
+ render(
298
+ <ProductCard
299
+ product={product}
300
+ favoriteButtonDisabled
301
+ onFavoriteToggled={onFavoriteToggled}
302
+ />
303
+ )
304
+
305
+ // Button should not exist at all
306
+ const favoriteButton = screen.queryByRole('button')
307
+ expect(favoriteButton).not.toBeInTheDocument()
308
+
309
+ // Callbacks should not be called
310
+ expect(onFavoriteToggled).not.toHaveBeenCalled()
311
+ expect(mockMinisSDK.saveProduct).not.toHaveBeenCalled()
312
+ expect(mockMinisSDK.unsaveProduct).not.toHaveBeenCalled()
313
+ })
265
314
  })
266
315
 
267
316
  describe('Custom Composition', () => {
@@ -307,6 +356,25 @@ describe('ProductCard', () => {
307
356
  expect(screen.getByText(product.title)).toBeInTheDocument()
308
357
  expect(screen.getByText('$99.99')).toBeInTheDocument()
309
358
  })
359
+
360
+ it('respects favoriteButtonDisabled in custom composition', () => {
361
+ const product = mockProduct()
362
+
363
+ render(
364
+ <ProductCard product={product} favoriteButtonDisabled>
365
+ <ProductCardContainer>
366
+ <ProductCardImageContainer>
367
+ <ProductCardImage />
368
+ <ProductCardFavoriteButton />
369
+ </ProductCardImageContainer>
370
+ </ProductCardContainer>
371
+ </ProductCard>
372
+ )
373
+
374
+ // Favorite button should not be rendered even when explicitly included
375
+ const favoriteButton = screen.queryByRole('button')
376
+ expect(favoriteButton).not.toBeInTheDocument()
377
+ })
310
378
  })
311
379
 
312
380
  describe('Edge Cases', () => {
@@ -28,6 +28,7 @@ interface ProductCardContextValue {
28
28
 
29
29
  // State
30
30
  isFavorited: boolean
31
+ isFavoriteButtonDisabled: boolean
31
32
 
32
33
  // Actions
33
34
  onClick: () => void
@@ -194,7 +195,10 @@ function ProductCardFavoriteButton({
194
195
  className,
195
196
  ...props
196
197
  }: React.ComponentProps<'div'>) {
197
- const {isFavorited, onFavoriteToggle} = useProductCardContext()
198
+ const {isFavorited, isFavoriteButtonDisabled, onFavoriteToggle} =
199
+ useProductCardContext()
200
+ if (isFavoriteButtonDisabled) return null
201
+
198
202
  return (
199
203
  <div className={cn('absolute bottom-3 right-3 z-10', className)} {...props}>
200
204
  <FavoriteButton onClick={onFavoriteToggle} filled={isFavorited} />
@@ -292,6 +296,8 @@ export interface ProductCardProps {
292
296
  onFavoriteToggled?: (isFavorited: boolean) => void
293
297
  /** Custom layout via children */
294
298
  children?: React.ReactNode
299
+ /** Whether the favorite button is disabled */
300
+ favoriteButtonDisabled?: boolean
295
301
  }
296
302
 
297
303
  function ProductCard({
@@ -304,6 +310,7 @@ function ProductCard({
304
310
  onProductClick,
305
311
  onFavoriteToggled,
306
312
  children,
313
+ favoriteButtonDisabled = false,
307
314
  }: ProductCardProps) {
308
315
  const {navigateToProduct} = useShopNavigation()
309
316
  const {saveProduct, unsaveProduct} = useSavedProductsActions()
@@ -374,7 +381,7 @@ function ProductCard({
374
381
 
375
382
  // State
376
383
  isFavorited: isFavoritedLocal,
377
-
384
+ isFavoriteButtonDisabled: favoriteButtonDisabled,
378
385
  // Actions
379
386
  onClick: handleClick,
380
387
  onFavoriteToggle: handleFavoriteClick,
@@ -389,6 +396,7 @@ function ProductCard({
389
396
  isFavoritedLocal,
390
397
  handleClick,
391
398
  handleFavoriteClick,
399
+ favoriteButtonDisabled,
392
400
  ]
393
401
  )
394
402
 
@@ -184,6 +184,53 @@ describe('ProductLink', () => {
184
184
  // Should not have favorite button
185
185
  expect(screen.queryByRole('button')).not.toBeInTheDocument()
186
186
  })
187
+
188
+ it('renders custom action when provided', () => {
189
+ const product = mockProduct()
190
+ const onCustomActionClick = vi.fn()
191
+ render(
192
+ <ProductLink
193
+ product={product}
194
+ customAction={
195
+ <button type="button" data-testid="custom-cta">
196
+ Add to Cart
197
+ </button>
198
+ }
199
+ onCustomActionClick={onCustomActionClick}
200
+ />
201
+ )
202
+
203
+ // Custom action should be rendered
204
+ expect(screen.getByTestId('custom-cta')).toBeInTheDocument()
205
+ expect(screen.getByText('Add to Cart')).toBeInTheDocument()
206
+
207
+ // Favorite button should not be rendered when custom action is present
208
+ // Check that there's only one button (the custom one)
209
+ const buttons = screen.getAllByRole('button')
210
+ expect(buttons).toHaveLength(1)
211
+ expect(buttons[0]).toHaveAttribute('data-testid', 'custom-cta')
212
+ })
213
+
214
+ it('renders custom action even when hideFavoriteAction is true', () => {
215
+ const product = mockProduct()
216
+ const onCustomActionClick = vi.fn()
217
+ render(
218
+ <ProductLink
219
+ product={product}
220
+ hideFavoriteAction
221
+ customAction={
222
+ <button type="button" data-testid="custom-action">
223
+ Buy Now
224
+ </button>
225
+ }
226
+ onCustomActionClick={onCustomActionClick}
227
+ />
228
+ )
229
+
230
+ // Custom action should still be rendered
231
+ expect(screen.getByTestId('custom-action')).toBeInTheDocument()
232
+ expect(screen.getByText('Buy Now')).toBeInTheDocument()
233
+ })
187
234
  })
188
235
 
189
236
  describe('Interactions', () => {
@@ -313,6 +360,40 @@ describe('ProductLink', () => {
313
360
  expect(onClick).not.toHaveBeenCalled()
314
361
  expect(mockMinisSDK.navigateToProduct).not.toHaveBeenCalled()
315
362
  })
363
+
364
+ it('calls onCustomActionClick when custom action is clicked', async () => {
365
+ const user = userEvent.setup()
366
+ const product = mockProduct()
367
+ const onClick = vi.fn()
368
+ const onCustomActionClick = vi.fn()
369
+
370
+ render(
371
+ <ProductLink
372
+ product={product}
373
+ onClick={onClick}
374
+ customAction={
375
+ <button type="button" data-testid="custom-btn">
376
+ Quick Add
377
+ </button>
378
+ }
379
+ onCustomActionClick={onCustomActionClick}
380
+ />
381
+ )
382
+
383
+ const customButton = screen.getByTestId('custom-btn')
384
+ await user.click(customButton)
385
+
386
+ // Should call custom action handler
387
+ expect(onCustomActionClick).toHaveBeenCalledTimes(1)
388
+
389
+ // Should NOT call favorite save/unsave
390
+ expect(mockMinisSDK.saveProduct).not.toHaveBeenCalled()
391
+ expect(mockMinisSDK.unsaveProduct).not.toHaveBeenCalled()
392
+
393
+ // Should NOT trigger product navigation
394
+ expect(onClick).not.toHaveBeenCalled()
395
+ expect(mockMinisSDK.navigateToProduct).not.toHaveBeenCalled()
396
+ })
316
397
  })
317
398
 
318
399
  describe('Star Rating Display', () => {