Compare commits
1 commit
main
...
cathryn/fi
Author | SHA1 | Date | |
---|---|---|---|
ccdcf93e33 |
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
locale: 'en-us',
|
||||
storeDomain: 'hydrogen-preview.myshopify.com',
|
||||
storefrontToken: '3b580e70970c4528da70c98e097c2fa0',
|
||||
storeDomain: 'react-advanced-hydrogen.myshopify.com',
|
||||
storefrontToken: '267baac86464f30e5a6cd11451406690',
|
||||
graphqlApiVersion: 'unstable',
|
||||
};
|
||||
|
|
|
@ -111,6 +111,11 @@ export default function ProductDetails({product}) {
|
|||
<>
|
||||
<Seo product={product} />
|
||||
<Product product={product} initialVariantId={initialVariant.id}>
|
||||
<Product.Metafield
|
||||
keyName="fileref"
|
||||
namespace="my_fields"
|
||||
className="w-full h-screen object-contain"
|
||||
/>
|
||||
<div className="grid grid-cols-1 md:grid-cols-[2fr,1fr] gap-x-8 my-16">
|
||||
<div className="md:hidden mt-5 mb-8">
|
||||
<Product.Title
|
||||
|
|
|
@ -132,6 +132,7 @@ const QUERY = gql`
|
|||
$country: CountryCode
|
||||
$numCollections: Int = 2
|
||||
$numProducts: Int = 3
|
||||
$includeReferenceMetafieldDetails: Boolean = false
|
||||
$numProductMetafields: Int = 0
|
||||
$numProductVariants: Int = 250
|
||||
$numProductMedia: Int = 1
|
||||
|
|
|
@ -65,6 +65,7 @@ const QUERY = gql`
|
|||
$handle: String!
|
||||
$country: CountryCode
|
||||
$numProducts: Int!
|
||||
$includeReferenceMetafieldDetails: Boolean = false
|
||||
$numProductMetafields: Int = 0
|
||||
$numProductVariants: Int = 250
|
||||
$numProductMedia: Int = 6
|
||||
|
|
|
@ -32,6 +32,7 @@ const QUERY = gql`
|
|||
query product(
|
||||
$country: CountryCode
|
||||
$handle: String!
|
||||
$includeReferenceMetafieldDetails: Boolean = true
|
||||
$numProductMetafields: Int = 20
|
||||
$numProductVariants: Int = 250
|
||||
$numProductMedia: Int = 6
|
||||
|
|
|
@ -5,7 +5,9 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
<!-- ## Unreleased -->
|
||||
## Unreleased
|
||||
|
||||
- feat: add file reference metafield support
|
||||
|
||||
## 0.6.3 - 2021-11-10
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import {StarRating} from './components/StarRating';
|
|||
import {RawHtml} from '../RawHtml';
|
||||
import {ParsedMetafield, Measurement, Rating} from '../../types';
|
||||
import {MetafieldFragment as Fragment} from '../../graphql/graphql-constants';
|
||||
import {Image} from '../Image';
|
||||
|
||||
export interface MetafieldProps {
|
||||
/** A [Metafield object](/api/storefront/reference/common-objects/metafield) from the Storefront API. */
|
||||
|
@ -103,6 +104,16 @@ export function Metafield<TTag extends ElementType>(
|
|||
{JSON.stringify(metafield.value)}
|
||||
</Wrapper>
|
||||
);
|
||||
case 'file_reference': {
|
||||
if (
|
||||
metafield.reference?.__typename === 'MediaImage' &&
|
||||
metafield.reference.image != null
|
||||
) {
|
||||
return (
|
||||
<Image image={metafield.reference.image} {...passthroughProps} />
|
||||
);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
const Wrapper = as ?? 'span';
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#import '../Image/ImageFragment.graphql'
|
||||
|
||||
fragment MetafieldFragment on Metafield {
|
||||
id
|
||||
type
|
||||
|
@ -7,4 +9,13 @@ fragment MetafieldFragment on Metafield {
|
|||
createdAt
|
||||
updatedAt
|
||||
description
|
||||
reference @include(if: $includeReferenceMetafieldDetails) {
|
||||
__typename
|
||||
... on MediaImage {
|
||||
mediaContentType
|
||||
image {
|
||||
...ImageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import * as Types from '../../graphql/types/types';
|
||||
|
||||
import {ImageFragmentFragment} from '../Image/ImageFragment';
|
||||
export type MetafieldFragmentFragment = {__typename?: 'Metafield'} & Pick<
|
||||
Types.Metafield,
|
||||
| 'id'
|
||||
|
@ -10,4 +11,16 @@ export type MetafieldFragmentFragment = {__typename?: 'Metafield'} & Pick<
|
|||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
| 'description'
|
||||
>;
|
||||
> & {
|
||||
reference?: Types.Maybe<
|
||||
| ({__typename: 'MediaImage'} & Pick<
|
||||
Types.MediaImage,
|
||||
'mediaContentType'
|
||||
> & {
|
||||
image?: Types.Maybe<{__typename?: 'Image'} & ImageFragmentFragment>;
|
||||
})
|
||||
| {__typename: 'Page'}
|
||||
| {__typename: 'Product'}
|
||||
| {__typename: 'ProductVariant'}
|
||||
>;
|
||||
};
|
||||
|
|
|
@ -3,7 +3,9 @@ import {Metafield} from '../Metafield.client';
|
|||
import {getParsedMetafield} from '../../../utilities/tests/metafields';
|
||||
import {mountWithShopifyProvider} from '../../../utilities/tests/shopify_provider';
|
||||
import {RawHtml} from '../../RawHtml';
|
||||
import {Image} from '../../Image';
|
||||
import {StarRating} from '../components';
|
||||
import {getMediaImage} from '../../../utilities/tests/media';
|
||||
|
||||
describe('<Metafield />', () => {
|
||||
it('renders nothing when the metafield value is undefined', () => {
|
||||
|
@ -936,68 +938,98 @@ describe('<Metafield />', () => {
|
|||
});
|
||||
|
||||
describe('with `file_reference` type metafield', () => {
|
||||
it('renders the file reference as a string in a `span` by default', () => {
|
||||
const metafield = getParsedMetafield({type: 'file_reference'});
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield} />
|
||||
);
|
||||
describe('when the reference type is a MediaImage', () => {
|
||||
it('renders an Image component', () => {
|
||||
const metafield = getParsedMetafield({
|
||||
type: 'file_reference',
|
||||
reference: {__typename: 'MediaImage', ...getMediaImage()},
|
||||
});
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield} />
|
||||
);
|
||||
|
||||
expect(component).toContainReactComponent('span', {
|
||||
children: metafield.value,
|
||||
expect(component).toContainReactComponent(Image);
|
||||
});
|
||||
|
||||
it.only('allows passthrough props', () => {
|
||||
const metafield = getParsedMetafield({
|
||||
type: 'file_reference',
|
||||
reference: {__typename: 'MediaImage', ...getMediaImage()},
|
||||
});
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield} className="rounded-md" />
|
||||
);
|
||||
|
||||
expect(component).toContainReactComponent(Image, {
|
||||
className: 'rounded-md',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the file reference as a string in the element specified by the `as` prop', () => {
|
||||
const metafield = getParsedMetafield({type: 'file_reference'});
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield} as="p" />
|
||||
);
|
||||
describe('when the reference type is not a MediaImage', () => {
|
||||
it('renders the file reference as a string in a `span` by default', () => {
|
||||
const metafield = getParsedMetafield({type: 'file_reference'});
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield} />
|
||||
);
|
||||
|
||||
expect(component).toContainReactComponent('p', {
|
||||
children: metafield.value,
|
||||
expect(component).toContainReactComponent('span', {
|
||||
children: metafield.value,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('passes the metafield as a render prop to the children render function', () => {
|
||||
const children = jest.fn().mockImplementation(() => {
|
||||
return null;
|
||||
it('renders the file reference as a string in the element specified by the `as` prop', () => {
|
||||
const metafield = getParsedMetafield({type: 'file_reference'});
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield} as="p" />
|
||||
);
|
||||
|
||||
expect(component).toContainReactComponent('p', {
|
||||
children: metafield.value,
|
||||
});
|
||||
});
|
||||
const metafield = getParsedMetafield({type: 'file_reference'});
|
||||
|
||||
mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield}>{children}</Metafield>
|
||||
);
|
||||
it('passes the metafield as a render prop to the children render function', () => {
|
||||
const children = jest.fn().mockImplementation(() => {
|
||||
return null;
|
||||
});
|
||||
const metafield = getParsedMetafield({type: 'file_reference'});
|
||||
|
||||
expect(children).toHaveBeenCalledWith({
|
||||
...metafield,
|
||||
value: metafield.value,
|
||||
mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield}>{children}</Metafield>
|
||||
);
|
||||
|
||||
expect(children).toHaveBeenCalledWith({
|
||||
...metafield,
|
||||
value: metafield.value,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders its children', () => {
|
||||
const metafield = getParsedMetafield({type: 'file_reference'});
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield}>
|
||||
{({value}) => {
|
||||
return <p>The reference is {value}</p>;
|
||||
}}
|
||||
</Metafield>
|
||||
);
|
||||
it('renders its children', () => {
|
||||
const metafield = getParsedMetafield({type: 'file_reference'});
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield metafield={metafield}>
|
||||
{({value}) => {
|
||||
return <p>The reference is {value}</p>;
|
||||
}}
|
||||
</Metafield>
|
||||
);
|
||||
|
||||
expect(component).toContainReactComponent('p', {
|
||||
children: [`The reference is `, metafield.value],
|
||||
expect(component).toContainReactComponent('p', {
|
||||
children: [`The reference is `, metafield.value],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('allows passthrough props', () => {
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield
|
||||
metafield={getParsedMetafield({type: 'file_reference'})}
|
||||
className="emphasized"
|
||||
/>
|
||||
);
|
||||
expect(component).toContainReactComponent('span', {
|
||||
className: 'emphasized',
|
||||
it('allows passthrough props', () => {
|
||||
const component = mountWithShopifyProvider(
|
||||
<Metafield
|
||||
metafield={getParsedMetafield({type: 'file_reference'})}
|
||||
className="emphasized"
|
||||
/>
|
||||
);
|
||||
expect(component).toContainReactComponent('span', {
|
||||
className: 'emphasized',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {createContext} from 'react';
|
||||
import {ProductOptionsHookValue} from '../../hooks';
|
||||
import {GraphQLConnection, ParsedMetafield} from '../../types';
|
||||
import {GraphQLConnection, ParsedMetafield, RawMetafield} from '../../types';
|
||||
import {ProductProviderFragmentFragment} from './ProductProviderFragment';
|
||||
import {Product} from './types';
|
||||
import {Collection, Image} from '../../graphql/types/types';
|
||||
|
@ -21,7 +21,7 @@ export type ProductContextType = Omit<
|
|||
media?: ProductProviderFragmentFragment['media']['edges'][0]['node'][];
|
||||
mediaConnection?: ProductProviderFragmentFragment['media'];
|
||||
metafields?: ParsedMetafield[];
|
||||
metafieldsConnection?: ProductProviderFragmentFragment['metafields'];
|
||||
metafieldsConnection?: GraphQLConnection<RawMetafield>;
|
||||
images?: Partial<Image>[];
|
||||
imagesConnection?: GraphQLConnection<Partial<Image>>;
|
||||
collections?: Partial<Collection>[];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {SellingPlanGroup, Variant} from '../../hooks/useProductOptions';
|
||||
import {GraphQLConnection} from '../../types';
|
||||
import {GraphQLConnection, RawMetafield} from '../../types';
|
||||
import {ProductProviderFragmentFragment} from './ProductProviderFragment';
|
||||
import {ImageFragmentFragment} from '../Image/ImageFragment';
|
||||
import {Collection} from '../../graphql/types/types';
|
||||
|
@ -12,7 +12,7 @@ export interface Product {
|
|||
handle?: ProductProviderFragmentFragment['descriptionHtml'];
|
||||
id?: ProductProviderFragmentFragment['id'];
|
||||
media?: ProductProviderFragmentFragment['media'];
|
||||
metafields?: ProductProviderFragmentFragment['metafields'];
|
||||
metafields?: GraphQLConnection<RawMetafield>;
|
||||
priceRange?: Partial<ProductProviderFragmentFragment['priceRange']>;
|
||||
title?: ProductProviderFragmentFragment['title'];
|
||||
variants?: GraphQLConnection<Variant>;
|
||||
|
|
|
@ -2187,20 +2187,36 @@ fragment Model3DFragment on Model3d {
|
|||
`;
|
||||
|
||||
/**
|
||||
*```
|
||||
* fragment MetafieldFragment on Metafield {
|
||||
* id
|
||||
* type
|
||||
* namespace
|
||||
* key
|
||||
* value
|
||||
* createdAt
|
||||
* updatedAt
|
||||
* description
|
||||
* }
|
||||
|
||||
*```
|
||||
*/
|
||||
*```
|
||||
* fragment MetafieldFragment on Metafield {
|
||||
* id
|
||||
* type
|
||||
* namespace
|
||||
* key
|
||||
* value
|
||||
* createdAt
|
||||
* updatedAt
|
||||
* description
|
||||
* reference @include(if: $includeReferenceMetafieldDetails) {
|
||||
* __typename
|
||||
* ... on MediaImage {
|
||||
* mediaContentType
|
||||
* image {
|
||||
* ...ImageFragment
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* fragment ImageFragment on Image {
|
||||
* id
|
||||
* url
|
||||
* altText
|
||||
* width
|
||||
* height
|
||||
* }
|
||||
*
|
||||
*```
|
||||
*/
|
||||
export const MetafieldFragment: string = `fragment MetafieldFragment on Metafield {
|
||||
id
|
||||
type
|
||||
|
@ -2210,6 +2226,22 @@ export const MetafieldFragment: string = `fragment MetafieldFragment on Metafiel
|
|||
createdAt
|
||||
updatedAt
|
||||
description
|
||||
reference @include(if: $includeReferenceMetafieldDetails) {
|
||||
__typename
|
||||
... on MediaImage {
|
||||
mediaContentType
|
||||
image {
|
||||
...ImageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment ImageFragment on Image {
|
||||
id
|
||||
url
|
||||
altText
|
||||
width
|
||||
height
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -2344,6 +2376,15 @@ export const MoneyFragment: string = `fragment MoneyFragment on MoneyV2 {
|
|||
* createdAt
|
||||
* updatedAt
|
||||
* description
|
||||
* reference @include(if: $includeReferenceMetafieldDetails) {
|
||||
* __typename
|
||||
* ... on MediaImage {
|
||||
* mediaContentType
|
||||
* image {
|
||||
* ...ImageFragment
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* fragment VariantFragment on ProductVariant {
|
||||
|
@ -2453,6 +2494,14 @@ export const MoneyFragment: string = `fragment MoneyFragment on MoneyV2 {
|
|||
* }
|
||||
* }
|
||||
*
|
||||
* fragment ImageFragment on Image {
|
||||
* id
|
||||
* url
|
||||
* altText
|
||||
* width
|
||||
* height
|
||||
* }
|
||||
*
|
||||
*
|
||||
* fragment SellingPlanFragment on SellingPlan {
|
||||
* id
|
||||
|
@ -2633,6 +2682,15 @@ fragment MetafieldFragment on Metafield {
|
|||
createdAt
|
||||
updatedAt
|
||||
description
|
||||
reference @include(if: $includeReferenceMetafieldDetails) {
|
||||
__typename
|
||||
... on MediaImage {
|
||||
mediaContentType
|
||||
image {
|
||||
...ImageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment VariantFragment on ProductVariant {
|
||||
|
@ -2742,6 +2800,14 @@ fragment Model3DFragment on Model3d {
|
|||
}
|
||||
}
|
||||
|
||||
fragment ImageFragment on Image {
|
||||
id
|
||||
url
|
||||
altText
|
||||
width
|
||||
height
|
||||
}
|
||||
|
||||
|
||||
fragment SellingPlanFragment on SellingPlan {
|
||||
id
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {useMemo} from 'react';
|
||||
import {GraphQLConnection, ParsedMetafield} from '../../types';
|
||||
import {Metafield} from '../../graphql/types/types';
|
||||
import {GraphQLConnection, ParsedMetafield, RawMetafield} from '../../types';
|
||||
import {flattenConnection, parseMetafieldValue} from '../../utilities';
|
||||
|
||||
/**
|
||||
|
@ -8,7 +7,7 @@ import {flattenConnection, parseMetafieldValue} from '../../utilities';
|
|||
* in an array of metafields whose `values` have been parsed according to the metafield `type`.
|
||||
*/
|
||||
export function useParsedMetafields(
|
||||
metafields: GraphQLConnection<Partial<Metafield>> | undefined
|
||||
metafields: GraphQLConnection<RawMetafield> | undefined
|
||||
): ParsedMetafield[] {
|
||||
return useMemo(() => {
|
||||
if (metafields == null) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type {ServerResponse} from 'http';
|
||||
import type {ServerComponentResponse} from './framework/Hydration/ServerComponentResponse.server';
|
||||
import type {ServerComponentRequest} from './framework/Hydration/ServerComponentRequest.server';
|
||||
import type {Metafield} from './graphql/types/types';
|
||||
import type {Metafield, MediaImage} from './graphql/types/types';
|
||||
|
||||
export type Renderer = (
|
||||
url: URL,
|
||||
|
@ -71,8 +71,14 @@ export interface GraphQLConnection<T> {
|
|||
edges?: {node: T}[];
|
||||
}
|
||||
|
||||
export type RawMetafield = Partial<Metafield>;
|
||||
export type ParsedMetafield = Omit<Partial<Metafield>, 'value'> & {
|
||||
export type RawMetafield = Omit<Partial<Metafield>, 'reference'> & {
|
||||
reference?: MediaImage;
|
||||
};
|
||||
|
||||
export type ParsedMetafield = Omit<
|
||||
Partial<Metafield>,
|
||||
'value' | 'reference'
|
||||
> & {
|
||||
value?:
|
||||
| string
|
||||
| number
|
||||
|
@ -81,6 +87,7 @@ export type ParsedMetafield = Omit<Partial<Metafield>, 'value'> & {
|
|||
| Date
|
||||
| Rating
|
||||
| Measurement;
|
||||
reference?: MediaImage | null;
|
||||
};
|
||||
|
||||
export interface Rating {
|
||||
|
|
|
@ -25,6 +25,9 @@ export function getPreviewImage(image: Partial<Image> = {}) {
|
|||
url: image.url ?? faker.random.image(),
|
||||
width: image.width ?? faker.datatype.number(),
|
||||
height: image.height ?? faker.datatype.number(),
|
||||
originalSrc: '',
|
||||
transformedSrc: '',
|
||||
src: '',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ export const METAFIELDS: MetafieldType[] = [
|
|||
|
||||
export function getRawMetafield(
|
||||
metafield: Partial<Metafield> & {type?: MetafieldType} = {}
|
||||
): Omit<Metafield, 'parentResource' | 'valueType'> {
|
||||
): RawMetafield {
|
||||
const type: MetafieldType =
|
||||
metafield.type == null
|
||||
? faker.random.arrayElement(METAFIELDS)
|
||||
|
@ -62,6 +62,7 @@ export function getRawMetafield(
|
|||
type,
|
||||
updatedAt: metafield.updatedAt ?? faker.date.recent(),
|
||||
value: metafield.value ?? getMetafieldValue(type),
|
||||
reference: metafield.reference as any,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue