Skip to content

Commit

Permalink
Add richtext section
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasKn committed Jan 11, 2024
1 parent c9fae4c commit 4d7fe07
Show file tree
Hide file tree
Showing 26 changed files with 803 additions and 63 deletions.
1 change: 1 addition & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type {InferType} from 'groqd';
import type {TypeFromSelection} from 'groqd';

import {Await, useLoaderData} from '@remix-run/react';
import {flattenConnection} from '@shopify/hydrogen';
import {Suspense} from 'react';

import type {ADD_TO_CART_BUTTON_BLOCK} from '~/qroq/blocks';
import type {ADD_TO_CART_BUTTON_BLOCK_FRAGMENT} from '~/qroq/blocks';
import type {loader} from '~/routes/($locale).products.$productHandle';

import {ProductForm} from '../product/ProductForm';

export type AddToCartButtonBlockProps = InferType<
typeof ADD_TO_CART_BUTTON_BLOCK
export type AddToCartButtonBlockProps = TypeFromSelection<
typeof ADD_TO_CART_BUTTON_BLOCK_FRAGMENT
>;

export function AddToCartButtonBlock(props: AddToCartButtonBlockProps) {
Expand Down
6 changes: 3 additions & 3 deletions templates/hydrogen-theme/app/components/blocks/PriceBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type {InferType} from 'groqd';
import type {TypeFromSelection} from 'groqd';

import {Await, useLoaderData} from '@remix-run/react';
import {flattenConnection} from '@shopify/hydrogen';
import {Suspense} from 'react';

import type {PRICE_BLOCK} from '~/qroq/blocks';
import type {PRICE_BLOCK_FRAGMENT} from '~/qroq/blocks';
import type {loader} from '~/routes/($locale).products.$productHandle';

import {VariantPrice} from '../product/VariantPrice';

export type PriceBlockProps = InferType<typeof PRICE_BLOCK>;
export type PriceBlockProps = TypeFromSelection<typeof PRICE_BLOCK_FRAGMENT>;

export function PriceBlock(props: PriceBlockProps) {
const loaderData = useLoaderData<typeof loader>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type {InferType} from 'groqd';
import type {InferType, TypeFromSelection} from 'groqd';

import {useLoaderData} from '@remix-run/react';

import type {SHOPIFY_DESCRIPTION_BLOCK} from '~/qroq/blocks';
import type {SHOPIFY_DESCRIPTION_BLOCK_FRAGMENT} from '~/qroq/blocks';
import type {loader} from '~/routes/($locale).products.$productHandle';

export type ShopifyDescriptionBlockProps = InferType<
typeof SHOPIFY_DESCRIPTION_BLOCK
export type ShopifyDescriptionBlockProps = TypeFromSelection<
typeof SHOPIFY_DESCRIPTION_BLOCK_FRAGMENT
>;

export function ShopifyDescriptionBlock(props: ShopifyDescriptionBlockProps) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type {InferType} from 'groqd';
import type {TypeFromSelection} from 'groqd';

import {useLoaderData} from '@remix-run/react';

import type {SHOPIFY_TITLE_BLOCK} from '~/qroq/blocks';
import type {SHOPIFY_TITLE_BLOCK_FRAGMENT} from '~/qroq/blocks';
import type {loader} from '~/routes/($locale).products.$productHandle';

export type ShopifyTitleBlockProps = InferType<typeof SHOPIFY_TITLE_BLOCK>;
export type ShopifyTitleBlockProps = TypeFromSelection<
typeof SHOPIFY_TITLE_BLOCK_FRAGMENT
>;

export function ShopifyTitleBlock(props: ShopifyTitleBlockProps) {
const {product} = useLoaderData<typeof loader>();
Expand Down
45 changes: 45 additions & 0 deletions templates/hydrogen-theme/app/components/cva/contentAlignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import type {VariantProps} from 'class-variance-authority';

import {cva} from 'class-variance-authority';

/*
|--------------------------------------------------------------------------
| Content Alignment
|--------------------------------------------------------------------------
*/
export type ContentAlignmentVariantProps = VariantProps<
typeof contentAlignment
>;
Expand All @@ -24,3 +29,43 @@ export const contentAlignment = cva('flex h-full', {

export const contentAlignmentVariants = (props: ContentAlignmentVariantProps) =>
contentAlignment(props);

/*
|--------------------------------------------------------------------------
| Text Alignment
|--------------------------------------------------------------------------
*/
export type TextAlignmentVariantProps = VariantProps<typeof textAlignment>;

export const textAlignment = cva('', {
variants: {
required: {
center: 'text-center',
left: 'text-left',
right: 'text-right',
},
},
});

export const textAlignmentVariants = (props: TextAlignmentVariantProps) =>
textAlignment(props);

/*
|--------------------------------------------------------------------------
| Content Position
|--------------------------------------------------------------------------
*/
export type ContentPositionVariantProps = VariantProps<typeof contentPosition>;

export const contentPosition = cva('', {
variants: {
required: {
center: 'mx-auto',
left: 'mr-auto',
right: 'ml-auto',
},
},
});

export const contentPositionVariants = (props: ContentPositionVariantProps) =>
contentPosition(props);
24 changes: 24 additions & 0 deletions templates/hydrogen-theme/app/components/icons/IconExternal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {cn} from '~/lib/utils';

import type {IconProps} from './Icon';

import {Icon} from './Icon';

export function IconExternal(props: IconProps) {
return (
<Icon
className={cn('size-5', props.className)}
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
viewBox="0 0 24 24"
>
<title>External</title>
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
<polyline points="15 3 21 3 21 9" />
<line x1="10" x2="21" y1="14" y2="3" />
</Icon>
);
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
import type {PortableTextComponents} from '@portabletext/react';
import type {PortableTextBlock} from '@portabletext/types';

import {PortableText} from '@portabletext/react';
import {useMemo} from 'react';

import type {AddToCartButtonBlockProps} from '../blocks/AddToCartButtonBlock';
import type {PriceBlockProps} from '../blocks/PriceBlock';
import type {ShopifyDescriptionBlockProps} from '../blocks/ShopifyDescriptionBlock';
import type {ShopifyTitleBlockProps} from '../blocks/ShopifyTitleBlock';
import type {ExternalLinkAnnotationProps} from '../sanity/richtext/components/ExternalLinkAnnotation';
import type {InternalLinkAnnotationProps} from '../sanity/richtext/components/InternalLinkAnnotation';
import type {ProductInformationSectionProps} from '../sections/ProductInformationSection';

import {AddToCartButtonBlock} from '../blocks/AddToCartButtonBlock';
import {PriceBlock} from '../blocks/PriceBlock';
import {ShopifyDescriptionBlock} from '../blocks/ShopifyDescriptionBlock';
import {ShopifyTitleBlock} from '../blocks/ShopifyTitleBlock';
import {ExternalLinkAnnotation} from '../sanity/richtext/components/ExternalLinkAnnotation';
import {InternalLinkAnnotation} from '../sanity/richtext/components/InternalLinkAnnotation';

export function ProductDetails(props: {data: ProductInformationSectionProps}) {
const components = useMemo(
() => ({
marks: {
externalLink: (props: {
children: React.ReactNode;
value: ExternalLinkAnnotationProps;
}) => {
return (
<ExternalLinkAnnotation {...props.value}>
{props.children}
</ExternalLinkAnnotation>
);
},
internalLink: (props: {
children: React.ReactNode;
value: InternalLinkAnnotationProps;
}) => {
return (
<InternalLinkAnnotation {...props.value}>
{props.children}
</InternalLinkAnnotation>
);
},
},
types: {
addToCartButton: (props: {value: AddToCartButtonBlockProps}) => {
return <AddToCartButtonBlock {...props.value} />;
Expand All @@ -36,7 +65,10 @@ export function ProductDetails(props: {data: ProductInformationSectionProps}) {
return (
<div className="space-y-4">
{props.data.richtext && (
<PortableText components={components} value={props.data.richtext} />
<PortableText
components={components as PortableTextComponents}
value={props.data.richtext as PortableTextBlock[]}
/>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type {InferType} from 'groqd';
import type {TypeFromSelection} from 'groqd';
import type {ProductVariantFragmentFragment} from 'storefrontapi.generated';

import {useLoaderData} from '@remix-run/react';

import type {ADD_TO_CART_BUTTON_BLOCK} from '~/qroq/blocks';
import type {ADD_TO_CART_BUTTON_BLOCK_FRAGMENT} from '~/qroq/blocks';
import type {loader} from '~/routes/($locale).products.$productHandle';

import {AddToCartForm} from './AddToCartForm';
Expand All @@ -12,7 +12,7 @@ import {VariantSelector} from './VariantSelector';
export function ProductForm(
props: {
variants: ProductVariantFragmentFragment[];
} & InferType<typeof ADD_TO_CART_BUTTON_BLOCK>,
} & TypeFromSelection<typeof ADD_TO_CART_BUTTON_BLOCK_FRAGMENT>,
) {
const {product} = useLoaderData<typeof loader>();
const showQuantitySelector = props.quantitySelector;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {vercelStegaCleanAll} from '@sanity/client/stega';
import {cx} from 'class-variance-authority';

import type {simpleContentAlignmentValues} from '~/qroq/sections';

import {
contentPosition,
textAlignment,
} from '~/components/cva/contentAlignment';

type AlignmentValues = (typeof simpleContentAlignmentValues)[number];

export function RichtextLayout(props: {
children: React.ReactNode;
contentAligment?: AlignmentValues | null;
desktopContentPosition?: AlignmentValues | null;
maxWidth?: null | number;
}) {
const style = {
'--maxWidth': props.maxWidth ? `${props.maxWidth}px` : 'auto',
} as React.CSSProperties;

const cleanContentAlignement = vercelStegaCleanAll(props.contentAligment);
const cleanContentPosition = vercelStegaCleanAll(
props.desktopContentPosition,
);

return (
<div
className={cx([
textAlignment({
required: cleanContentAlignement,
}),
contentPosition({
required: cleanContentPosition,
}),
'max-w-[var(--maxWidth)] space-y-2 overflow-hidden',
'[&_blockquote]:border-l-2 [&_blockquote]:pl-6 [&_blockquote]:italic',
'[&_ul>li]:mt-2 [&_ul]:list-inside [&_ul]:list-disc',
'[&_ol>li]:mt-2 [&_ol]:list-inside [&_ol]:list-decimal',
])}
style={style}
>
{props.children}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type {TypeFromSelection} from 'groqd';

import type {BUTTON_BLOCK_FRAGMENT} from '~/qroq/blocks';

import {Button} from '~/components/ui/Button';

import {SanityInternalLink} from '../../link/SanityInternalLink';

export type ButtonBlockProps = TypeFromSelection<typeof BUTTON_BLOCK_FRAGMENT>;

export function ButtonBlock(props: ButtonBlockProps) {
return (
<Button asChild>
<SanityInternalLink
data={{
_key: props._key,
_type: 'internalLink',
anchor: props.anchor,
link: props.link,
name: null,
}}
>
{props.label}
</SanityInternalLink>
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {TypeFromSelection} from 'groqd';

import type {EXTERNAL_LINK_BLOCK_ANNOTATION_FRAGMENT} from '~/qroq/blocks';

import {IconExternal} from '~/components/icons/IconExternal';
import {cn} from '~/lib/utils';

import {SanityExternalLink} from '../../link/SanityExternalLink';
import {richTextLinkClassName} from './InternalLinkAnnotation';

export type ExternalLinkAnnotationProps = TypeFromSelection<
typeof EXTERNAL_LINK_BLOCK_ANNOTATION_FRAGMENT
>;

export function ExternalLinkAnnotation(
props: ExternalLinkAnnotationProps & {children: React.ReactNode},
) {
return (
<SanityExternalLink
className={cn(richTextLinkClassName, 'inline-flex items-center gap-1')}
data={{
_key: props._key,
_type: 'externalLink',
link: props.link,
name: null,
openInNewTab: props.openInNewTab,
}}
>
{props.children}
{props.openInNewTab && <IconExternal className="size-3" />}
</SanityExternalLink>
);
}
Loading

0 comments on commit 4d7fe07

Please sign in to comment.