@shopify/shop-minis-react 0.0.27 → 0.0.29

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 (40) hide show
  1. package/dist/_virtual/index2.js +4 -4
  2. package/dist/_virtual/index3.js +4 -4
  3. package/dist/components/atoms/content-monitor.js +22 -0
  4. package/dist/components/atoms/content-monitor.js.map +1 -0
  5. package/dist/components/atoms/content-wrapper.js +18 -0
  6. package/dist/components/atoms/content-wrapper.js.map +1 -0
  7. package/dist/components/atoms/long-press-detector.js +33 -0
  8. package/dist/components/atoms/long-press-detector.js.map +1 -0
  9. package/dist/components/commerce/product-link.js +117 -113
  10. package/dist/components/commerce/product-link.js.map +1 -1
  11. package/dist/components/commerce/search.js +26 -17
  12. package/dist/components/commerce/search.js.map +1 -1
  13. package/dist/components/content/image-content-wrapper.js +27 -0
  14. package/dist/components/content/image-content-wrapper.js.map +1 -0
  15. package/dist/components/navigation/minis-router.js +14 -0
  16. package/dist/components/navigation/minis-router.js.map +1 -0
  17. package/dist/index.js +214 -212
  18. package/dist/index.js.map +1 -1
  19. package/dist/shop-minis-react/node_modules/.pnpm/react-router@7.7.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-router/dist/development/chunk-EF7DTUVF.js +764 -567
  20. package/dist/shop-minis-react/node_modules/.pnpm/react-router@7.7.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-router/dist/development/chunk-EF7DTUVF.js.map +1 -1
  21. package/dist/shop-minis-react/node_modules/.pnpm/video.js@8.23.3/node_modules/video.js/dist/video.es.js +1 -1
  22. package/dist/utils/colors.js +1 -1
  23. package/package.json +1 -1
  24. package/src/components/atoms/content-monitor.tsx +25 -0
  25. package/src/components/{content → atoms}/content-wrapper.tsx +1 -0
  26. package/src/components/atoms/long-press-detector.tsx +52 -0
  27. package/src/components/commerce/product-link.tsx +10 -4
  28. package/src/components/commerce/search.tsx +8 -1
  29. package/src/components/content/image-content-wrapper.tsx +42 -0
  30. package/src/components/index.ts +3 -2
  31. package/src/components/navigation/minis-router.tsx +23 -0
  32. package/src/index.css +1 -0
  33. package/src/stories/Search.stories.tsx +37 -0
  34. package/src/styles/fonts.css +26 -0
  35. package/src/styles/theme.css +26 -0
  36. package/dist/components/content/content-monitor.js +0 -17
  37. package/dist/components/content/content-monitor.js.map +0 -1
  38. package/dist/components/content/content-wrapper.js +0 -17
  39. package/dist/components/content/content-wrapper.js.map +0 -1
  40. package/src/components/content/content-monitor.tsx +0 -23
@@ -1,6 +1,6 @@
1
1
  import S from "../../../../../../../_virtual/window.js";
2
2
  import B from "../../../../../../../_virtual/document.js";
3
- import il from "../../../../../../../_virtual/index3.js";
3
+ import il from "../../../../../../../_virtual/index2.js";
4
4
  import ao from "../../../../../../../_virtual/browser-index.js";
5
5
  import xe from "../../../../@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/extends.js";
6
6
  import Kc from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-utils/es/resolve-url.js";
@@ -1,4 +1,4 @@
1
- import o from "../_virtual/index2.js";
1
+ import o from "../_virtual/index3.js";
2
2
  const a = (r) => o(r).darken(0.2).isDark();
3
3
  export {
4
4
  a as isDarkColor
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.0.27",
4
+ "version": "0.0.29",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {
@@ -0,0 +1,25 @@
1
+ import {useShopActions} from '../../internal/useShopActions'
2
+
3
+ import {LongPressDetector} from './long-press-detector'
4
+
5
+ export function ContentMonitor({
6
+ publicId,
7
+ children,
8
+ }: {
9
+ publicId?: string
10
+ children: React.ReactNode
11
+ }) {
12
+ const {showFeedbackSheet} = useShopActions()
13
+
14
+ return (
15
+ <LongPressDetector
16
+ onLongPress={() => {
17
+ if (!publicId) return
18
+
19
+ showFeedbackSheet({publicId})
20
+ }}
21
+ >
22
+ {children}
23
+ </LongPressDetector>
24
+ )
25
+ }
@@ -40,6 +40,7 @@ export function ContentWrapper({
40
40
  }: ContentWrapperProps) {
41
41
  const {content, loading} = useContent({
42
42
  identifiers: [{publicId, externalId}],
43
+ skip: !publicId && !externalId,
43
44
  })
44
45
 
45
46
  const contentItem = content?.[0]
@@ -0,0 +1,52 @@
1
+ import * as React from 'react'
2
+
3
+ import {motion, HTMLMotionProps} from 'motion/react'
4
+
5
+ interface LongPressDetectorProps extends HTMLMotionProps<'div'> {
6
+ onLongPress: () => void
7
+ delay?: number
8
+ children: React.ReactNode
9
+ }
10
+
11
+ export const LongPressDetector = ({
12
+ onLongPress,
13
+ delay = 500,
14
+ children,
15
+ ...motionProps
16
+ }: LongPressDetectorProps) => {
17
+ const longPressTimeoutRef = React.useRef<number | undefined>(undefined)
18
+
19
+ const handleTapStart = React.useCallback(() => {
20
+ longPressTimeoutRef.current = window.setTimeout(() => {
21
+ onLongPress()
22
+ longPressTimeoutRef.current = undefined
23
+ }, delay)
24
+ }, [onLongPress, delay])
25
+
26
+ const handleTapEnd = React.useCallback(() => {
27
+ if (longPressTimeoutRef.current) {
28
+ clearTimeout(longPressTimeoutRef.current)
29
+ longPressTimeoutRef.current = undefined
30
+ }
31
+ }, [])
32
+
33
+ // Cleanup timer on unmount
34
+ React.useEffect(() => {
35
+ return () => {
36
+ if (longPressTimeoutRef.current) {
37
+ clearTimeout(longPressTimeoutRef.current)
38
+ }
39
+ }
40
+ }, [])
41
+
42
+ return (
43
+ <motion.div
44
+ onTapStart={handleTapStart}
45
+ onTap={handleTapEnd}
46
+ onTapCancel={handleTapEnd}
47
+ {...motionProps}
48
+ >
49
+ {children}
50
+ </motion.div>
51
+ )
52
+ }
@@ -223,10 +223,15 @@ function ProductLinkActions({
223
223
  export interface ProductLinkProps {
224
224
  product: Product
225
225
  hideFavoriteAction?: boolean
226
+ onClick?: (product: Product) => void
226
227
  }
227
228
 
228
229
  // Composed ProductLink component
229
- function ProductLink({product, hideFavoriteAction = false}: ProductLinkProps) {
230
+ function ProductLink({
231
+ product,
232
+ hideFavoriteAction = false,
233
+ onClick,
234
+ }: ProductLinkProps) {
230
235
  const {navigateToProduct} = useShopNavigation()
231
236
  const {saveProduct, unsaveProduct} = useSavedProductsActions()
232
237
 
@@ -262,7 +267,8 @@ function ProductLink({product, hideFavoriteAction = false}: ProductLinkProps) {
262
267
  navigateToProduct({
263
268
  productId: id,
264
269
  })
265
- }, [navigateToProduct, id])
270
+ onClick?.(product)
271
+ }, [navigateToProduct, id, onClick, product])
266
272
 
267
273
  const handleActionPress = React.useCallback(async () => {
268
274
  const previousState = isFavoritedLocal
@@ -321,7 +327,7 @@ function ProductLink({product, hideFavoriteAction = false}: ProductLinkProps) {
321
327
  <ProductLinkInfo layout="horizontal">
322
328
  <ProductLinkTitle>{title}</ProductLinkTitle>
323
329
 
324
- {reviewCount && averageRating && (
330
+ {reviewCount && averageRating ? (
325
331
  <ProductLinkRating>
326
332
  <div className="flex items-center gap-1">
327
333
  {Array.from({length: 5}, (_, i) => (
@@ -343,7 +349,7 @@ function ProductLink({product, hideFavoriteAction = false}: ProductLinkProps) {
343
349
  </span>
344
350
  </div>
345
351
  </ProductLinkRating>
346
- )}
352
+ ) : null}
347
353
 
348
354
  <ProductLinkPrice>
349
355
  {hasDiscount ? (
@@ -220,6 +220,7 @@ export interface SearchResultsProps
220
220
  SearchInputProps,
221
221
  SearchResultsListProps {
222
222
  showSearchInput?: boolean
223
+ onProductClick?: (product: Product) => void
223
224
  }
224
225
 
225
226
  function Search({
@@ -230,6 +231,7 @@ function Search({
230
231
  className,
231
232
  renderItem,
232
233
  itemHeight,
234
+ onProductClick,
233
235
  }: SearchResultsProps) {
234
236
  const _renderItem = (product: Product, index: number) => {
235
237
  if (renderItem) {
@@ -238,7 +240,12 @@ function Search({
238
240
 
239
241
  return (
240
242
  <div className="p-2">
241
- <ProductLink key={product.id} product={product} hideFavoriteAction />
243
+ <ProductLink
244
+ key={product.id}
245
+ product={product}
246
+ hideFavoriteAction
247
+ onClick={onProductClick}
248
+ />
242
249
  </div>
243
250
  )
244
251
  }
@@ -0,0 +1,42 @@
1
+ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
2
+ import {ContentWrapper} from '../atoms/content-wrapper'
3
+
4
+ type ImageContentWrapperProps = (
5
+ | {publicId: string; externalId?: never}
6
+ | {externalId: string; publicId?: never}
7
+ ) & {
8
+ onLoad?: () => void
9
+ width?: number
10
+ height?: number
11
+ className?: string
12
+ Loader?: React.ReactNode | string
13
+ }
14
+
15
+ export function ImageContentWrapper({
16
+ onLoad,
17
+ width,
18
+ height,
19
+ className,
20
+ publicId,
21
+ externalId,
22
+ Loader,
23
+ }: ImageContentWrapperProps) {
24
+ return (
25
+ <ContentWrapper {...(externalId ? {externalId} : {publicId: publicId!})}>
26
+ {({content, loading}) => {
27
+ if (loading) return Loader ? <>{Loader}</> : null
28
+
29
+ return (
30
+ <img
31
+ src={content?.image?.url}
32
+ width={width}
33
+ height={height}
34
+ alt={content?.title}
35
+ onLoad={onLoad}
36
+ className={className}
37
+ />
38
+ )
39
+ }}
40
+ </ContentWrapper>
41
+ )
42
+ }
@@ -8,9 +8,9 @@ export * from './commerce/merchant-card-skeleton'
8
8
  export * from './commerce/quantity-selector'
9
9
  export * from './commerce/search'
10
10
 
11
- export * from './content/content-wrapper'
11
+ export * from './content/image-content-wrapper'
12
12
 
13
- export * from './navigation/transition-container'
13
+ export * from './navigation/minis-router'
14
14
  export * from './navigation/transition-link'
15
15
 
16
16
  export * from './atoms/button'
@@ -18,6 +18,7 @@ export * from './atoms/favorite-button'
18
18
  export * from './atoms/icon-button'
19
19
  export * from './atoms/thumbhash-image'
20
20
  export * from './atoms/touchable'
21
+ export * from './atoms/long-press-detector'
21
22
  export * from './atoms/alert-dialog'
22
23
  export * from './atoms/list'
23
24
  export * from './atoms/video-player'
@@ -0,0 +1,23 @@
1
+ import {BrowserRouter, BrowserRouterProps} from 'react-router'
2
+
3
+ import {TransitionContainer} from './transition-container'
4
+
5
+ type ShopMinisRouterProps = BrowserRouterProps & {
6
+ viewTransitions?: boolean
7
+ }
8
+
9
+ export function MinisRouter({
10
+ children,
11
+ viewTransitions = false,
12
+ ...props
13
+ }: ShopMinisRouterProps) {
14
+ if (viewTransitions) {
15
+ return (
16
+ <BrowserRouter {...props}>
17
+ <TransitionContainer>{children}</TransitionContainer>
18
+ </BrowserRouter>
19
+ )
20
+ }
21
+
22
+ return <BrowserRouter {...props}>{children}</BrowserRouter>
23
+ }
package/src/index.css CHANGED
@@ -1,6 +1,7 @@
1
1
  @import 'tailwindcss';
2
2
  @import 'tw-animate-css';
3
3
 
4
+ @import './styles/fonts.css';
4
5
  @import './styles/theme.css';
5
6
  @import './styles/globals.css';
6
7
  @import './styles/animations.css';
@@ -0,0 +1,37 @@
1
+ import {fn} from 'storybook/test'
2
+
3
+ import {
4
+ Search,
5
+ SearchInput,
6
+ SearchProvider,
7
+ SearchResultsList,
8
+ } from '../components'
9
+
10
+ import type {Meta, StoryObj} from '@storybook/react-vite'
11
+
12
+ const meta = {
13
+ title: 'Commerce/Search',
14
+ component: Search,
15
+ parameters: {
16
+ layout: 'padded',
17
+ },
18
+ tags: ['autodocs'],
19
+ } satisfies Meta<typeof Search>
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof meta>
23
+
24
+ export const Default: Story = {
25
+ args: {
26
+ onProductClick: fn(),
27
+ },
28
+ }
29
+
30
+ export const Composite: Story = {
31
+ render: args => (
32
+ <SearchProvider {...args}>
33
+ <SearchInput placeholder="Search..." />
34
+ <SearchResultsList />
35
+ </SearchProvider>
36
+ ),
37
+ }
@@ -0,0 +1,26 @@
1
+ @font-face {
2
+ font-family: 'Suisse Intl';
3
+ font-style: normal;
4
+ font-weight: 400;
5
+ font-display: swap;
6
+ src: url('https://cdn.shopify.com/shop-assets/static_uploads/shoplift/SuisseIntl-Book.woff2')
7
+ format('woff2');
8
+ }
9
+
10
+ @font-face {
11
+ font-family: 'Suisse Intl';
12
+ font-style: normal;
13
+ font-weight: 500;
14
+ font-display: swap;
15
+ src: url('https://cdn.shopify.com/shop-assets/static_uploads/shoplift/SuisseIntl-Medium.woff2')
16
+ format('woff2');
17
+ }
18
+
19
+ @font-face {
20
+ font-family: 'Suisse Intl';
21
+ font-style: normal;
22
+ font-weight: 600 700;
23
+ font-display: swap;
24
+ src: url('https://cdn.shopify.com/shop-assets/static_uploads/shoplift/SuisseIntl-SemiBold.woff2')
25
+ format('woff2');
26
+ }
@@ -94,6 +94,32 @@
94
94
 
95
95
  /* Button theme colors */
96
96
  --color-button-overlay: var(--grayscale-d70);
97
+
98
+ /* Default fonts */
99
+ --font-sans: 'Suisse Intl', ui-sans-serif, system-ui, sans-serif,
100
+ 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
101
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
102
+ 'Liberation Mono', 'Courier New', monospace;
103
+
104
+ /* Default Tailwind spacing scale (0.25rem = 4px base) */
105
+ --spacing: 0.25rem;
106
+
107
+ /* Semantic spacing aliases */
108
+ --spacing-shop-screen-margin: 20px;
109
+ --spacing--shop-screen-margin: -20px;
110
+ --spacing-shop-section-gap: 36px;
111
+ --spacing--shop-section-gap: -36px;
112
+ --spacing-shop-card-row-gutter: 8px;
113
+ --spacing--shop-card-row-gutter: -8px;
114
+ --spacing-shop-card-padding: 16px;
115
+ --spacing--shop-card-padding: -16px;
116
+
117
+ /* Shop shadow values */
118
+ --shadow-shop-xs: 0px 2px 4px 0px rgba(0, 0, 0, 0.06);
119
+ --shadow-shop-xs-surface: 0px -1px 4px 0px rgba(0, 0, 0, 0.06);
120
+ --shadow-shop-s: 0px 4px 8px 0px rgba(0, 0, 0, 0.1);
121
+ --shadow-shop-m: 0px 4px 12px 0px rgba(0, 0, 0, 0.16);
122
+ --shadow-shop-l: 0px 8px 24px 0px rgba(0, 0, 0, 0.24);
97
123
  }
98
124
 
99
125
  /* Dark mode overrides - only the palette colors that change */
@@ -1,17 +0,0 @@
1
- import { jsx as r } from "react/jsx-runtime";
2
- import { Touchable as t } from "../atoms/touchable.js";
3
- function i({
4
- // publicId,
5
- children: o
6
- }) {
7
- return /* @__PURE__ */ r(
8
- t,
9
- {
10
- children: o
11
- }
12
- );
13
- }
14
- export {
15
- i as ContentMonitor
16
- };
17
- //# sourceMappingURL=content-monitor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"content-monitor.js","sources":["../../../src/components/content/content-monitor.tsx"],"sourcesContent":["// import {useShopActions} from '../../internal/useShopActions'\nimport {Touchable} from '../atoms/touchable'\n\nexport function ContentMonitor({\n // publicId,\n children,\n}: {\n publicId: string\n children: React.ReactNode\n}) {\n // const {showFeedbackSheet} = useShopActions()\n\n return (\n <Touchable\n // TODO: Add long press support to Touchable\n // onLongPress={() => {\n // showFeedbackSheet({publicId})\n // }}\n >\n {children}\n </Touchable>\n )\n}\n"],"names":["ContentMonitor","children","jsx","Touchable"],"mappings":";;AAGO,SAASA,EAAe;AAAA;AAAA,EAE7B,UAAAC;AACF,GAGG;AAIC,SAAA,gBAAAC;AAAA,IAACC;AAAA,IAAA;AAAA,MAME,UAAAF;AAAA,IAAA;AAAA,EACH;AAEJ;"}
@@ -1,17 +0,0 @@
1
- import { jsx as c } from "react/jsx-runtime";
2
- import { useContent as p } from "../../hooks/content/useContent.js";
3
- import { ContentMonitor as m } from "./content-monitor.js";
4
- function d({
5
- publicId: r,
6
- externalId: e,
7
- children: o
8
- }) {
9
- const { content: i, loading: t } = p({
10
- identifiers: [{ publicId: r, externalId: e }]
11
- }), n = i?.[0];
12
- return t || !n ? o({ loading: t }) : /* @__PURE__ */ c(m, { publicId: n.publicId, children: o({ content: n, loading: t }) });
13
- }
14
- export {
15
- d as ContentWrapper
16
- };
17
- //# sourceMappingURL=content-wrapper.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"content-wrapper.js","sources":["../../../src/components/content/content-wrapper.tsx"],"sourcesContent":["import {useContent} from '../../hooks/content/useContent'\nimport {Content} from '../../types'\n\nimport {ContentMonitor} from './content-monitor'\n\ninterface BaseContentWrapperProps {\n children: ({\n content,\n loading,\n }: {\n content?: Content\n loading: boolean\n }) => JSX.Element | null\n}\n\ninterface PublicIdContentWrapperProps extends BaseContentWrapperProps {\n publicId: string\n externalId?: never\n}\n\ninterface ExternalIdContentWrapperProps extends BaseContentWrapperProps {\n externalId: string\n publicId?: never\n}\n\ntype ContentWrapperProps =\n | PublicIdContentWrapperProps\n | ExternalIdContentWrapperProps\n\n// It's too messy in the docs to show the complete types here so we show a simplified version\nexport interface ContentWrapperPropsForDocs extends BaseContentWrapperProps {\n publicId?: string\n externalId?: string\n}\n\nexport function ContentWrapper({\n publicId,\n externalId,\n children,\n}: ContentWrapperProps) {\n const {content, loading} = useContent({\n identifiers: [{publicId, externalId}],\n })\n\n const contentItem = content?.[0]\n\n if (loading || !contentItem) {\n return children({loading})\n }\n\n return (\n <ContentMonitor publicId={contentItem.publicId}>\n {children({content: contentItem, loading})}\n </ContentMonitor>\n )\n}\n"],"names":["ContentWrapper","publicId","externalId","children","content","loading","useContent","contentItem","jsx","ContentMonitor"],"mappings":";;;AAmCO,SAASA,EAAe;AAAA,EAC7B,UAAAC;AAAA,EACA,YAAAC;AAAA,EACA,UAAAC;AACF,GAAwB;AACtB,QAAM,EAAC,SAAAC,GAAS,SAAAC,EAAO,IAAIC,EAAW;AAAA,IACpC,aAAa,CAAC,EAAC,UAAAL,GAAU,YAAAC,EAAW,CAAA;AAAA,EAAA,CACrC,GAEKK,IAAcH,IAAU,CAAC;AAE3B,SAAAC,KAAW,CAACE,IACPJ,EAAS,EAAC,SAAAE,GAAQ,IAIzB,gBAAAG,EAACC,GAAe,EAAA,UAAUF,EAAY,UACnC,UAASJ,EAAA,EAAC,SAASI,GAAa,SAAAF,EAAO,CAAC,EAC3C,CAAA;AAEJ;"}
@@ -1,23 +0,0 @@
1
- // import {useShopActions} from '../../internal/useShopActions'
2
- import {Touchable} from '../atoms/touchable'
3
-
4
- export function ContentMonitor({
5
- // publicId,
6
- children,
7
- }: {
8
- publicId: string
9
- children: React.ReactNode
10
- }) {
11
- // const {showFeedbackSheet} = useShopActions()
12
-
13
- return (
14
- <Touchable
15
- // TODO: Add long press support to Touchable
16
- // onLongPress={() => {
17
- // showFeedbackSheet({publicId})
18
- // }}
19
- >
20
- {children}
21
- </Touchable>
22
- )
23
- }