Skip to content

Commit

Permalink
Merge pull request #324 from WestpacGEL/feature/site-foundation-content
Browse files Browse the repository at this point in the history
feat(site): add custom component blocks for foundation content
  • Loading branch information
samithaf authored Nov 23, 2023
2 parents aaa293f + a5b2afe commit efd8def
Show file tree
Hide file tree
Showing 45 changed files with 715 additions and 746 deletions.
3 changes: 3 additions & 0 deletions apps/site/keystatic.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { GitHubConfig, LocalConfig, collection, config, fields, singleton } from '@keystatic/core';

import { ArticleComponentBlocks } from '@/components/component-blocks/article-component-blocks';
import { foundationBlocks } from '@/components/component-blocks/foundation-blocks';

const storage: LocalConfig['storage'] | GitHubConfig['storage'] =
process.env.NODE_ENV === 'development'
Expand Down Expand Up @@ -121,6 +123,7 @@ export default config({
images: true,
layouts: [[6, 6]],
label: 'Design',
componentBlocks: foundationBlocks,
}),
}),
{
Expand Down
Binary file added apps/site/public/assets/GEL_Icons.zip
Binary file not shown.
Binary file added apps/site/public/assets/GEL_Pictograms.zip
Binary file not shown.
40 changes: 40 additions & 0 deletions apps/site/src/app/api/assets/_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import fs from 'fs';

/**
* Below functions are used to convert from node file stream to one that is accepted by NextResponse (which is an extension of the Web Response API)
*
* Taken from https://github.com/vercel/next.js/discussions/15453#discussioncomment-6226391
*/

// from https://github.com/MattMorgis/async-stream-generator
async function* nodeStreamToIterator(stream: fs.ReadStream) {
for await (const chunk of stream) {
yield chunk;
}
}

/**
* Taken from Next.js doc
* https://nextjs.org/docs/app/building-your-application/routing/router-handlers#streaming
*/
function iteratorToStream(iterator: any): ReadableStream {
return new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next();

if (done) {
controller.close();
} else {
// conversion to Uint8Array is important here otherwise the stream is not readable
// @see https://github.com/vercel/next.js/issues/38736
controller.enqueue(new Uint8Array(value));
}
},
});
}

export function streamFile(path: string): ReadableStream {
const downloadStream = fs.createReadStream(path);
const data: ReadableStream = iteratorToStream(nodeStreamToIterator(downloadStream));
return data;
}
30 changes: 30 additions & 0 deletions apps/site/src/app/api/assets/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NextResponse } from 'next/server';

import { streamFile } from './_utils';

export async function POST(request: Request) {
const publicDir = __dirname.split('.next')[0] + 'public/';
let filePath = `${publicDir}assets`;
const fomData = await request.formData();
const type = fomData.get('asset');

switch (type) {
case 'icon':
filePath = filePath + '/GEL_Icons.zip';
break;
case 'pictogram':
filePath = filePath + '/GEL_Pictograms.zip';
break;
case 'logo': // TODO: Update with logos/symbols assets once I get them from Justin
default:
return NextResponse.json({ error: 'Internal Server Error: asset type not found' }, { status: 500 });
}
const data: ReadableStream = streamFile(filePath);

return new NextResponse(data, {
status: 200,
headers: new Headers({
'content-type': 'application/zip',
}),
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { DocumentRenderer } from '@keystatic/core/renderer';
import { Container } from '@westpac/ui';
import { useMemo } from 'react';

import { Colors } from '@/components/component-blocks/colors/colors.component';
import { Icons } from '@/components/component-blocks/icons/icons.component';
import { Logos } from '@/components/component-blocks/logos/logos.component';
import { Pictograms } from '@/components/component-blocks/pictograms/pictograms.component';
import { Section } from '@/components/content-blocks/section';
import { Code, Heading } from '@/components/document-renderer';
import { RelatedInfo } from '@/components/related-info';
Expand Down Expand Up @@ -36,7 +40,12 @@ export function DesignContent({ designSections, description, relatedComponents }
code: props => <Code className="my-4" enableLiveCode={false} {...props} />,
},
}}
componentBlocks={{}}
componentBlocks={{
icons: () => <Icons />,
logos: () => <Logos />,
pictograms: () => <Pictograms />,
colors: props => <Colors palette={props.palette} />,
}}
/>
</Container>
</Section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ function Group({ label, level, crumbs, children, ...props }: GroupProps) {

function Item({ label, path, level, crumbs, brand, ...props }: ItemProps) {
const href = `/design-system/${path}?brand=${brand}`;
const active = crumbs[crumbs.length - 1] === label;
const page = path?.split('/').pop();
const active = crumbs[crumbs.length - 1] === page;
const { setOpen } = useSidebar();

return (
Expand Down
5 changes: 5 additions & 0 deletions apps/site/src/app/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
@tailwind components;
@tailwind utilities;

/* TEMP FIX: Increasing width for section editor dialogs as the toolbar options overlap when adding the custom component block option */
section.ksv-dialog-root {
width: var(--ksv-size-dialog-large);
}

/* GEL fonts */
@font-face {
src: url('/fonts/Graphik-Regular-Web.woff') format('woff');
Expand Down
10 changes: 9 additions & 1 deletion apps/site/src/components/code/code.inject-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,19 @@ export {
SettingsIcon,
WatchIcon,
EmailIcon,
AtmIcon,
EftposIcon,
PadlockIcon,
BusinessPersonIcon,
SecurityIcon,
VerifiedIcon,
GiftIcon,
WarningIcon,
PadlockIcon,
SuccessIcon,
} from '@westpac/ui/icon';

export { GiftPictogram } from '@westpac/ui/pictogram';
export { Link as NextLink };

export { IconSizesDemo } from './components/demos';
export { Hr, Body } from './components/utils';
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Grid, Item } from '@westpac/ui/grid';
import { AtmIcon, BusinessPersonIcon, EftposIcon, PadlockIcon, SecurityIcon, VerifiedIcon } from '@westpac/ui/icon';
import { Fragment } from 'react';

type IconSize = {
size: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
text: string;
};

export function IconSizesDemo() {
const sizes: IconSize[] = [
{ text: 'Extra small - 12px', size: 'xsmall' },
{ text: 'Small - 18px', size: 'small' },
{ text: 'Medium - 24px', size: 'medium' },
{ text: 'Large - 36px', size: 'large' },
{ text: 'Extra large - 48px', size: 'xlarge' },
];
return (
<Grid className="auto-rows-fr grid-cols-[repeat(7,_1fr)] items-center">
{sizes.map(s => (
<Fragment key={s.size}>
<Item>{s.text}</Item>
<GridItem>
<AtmIcon size={s.size} />
</GridItem>
<GridItem>
<EftposIcon size={s.size} />
</GridItem>
<GridItem>
<PadlockIcon size={s.size} />
</GridItem>
<GridItem>
<BusinessPersonIcon size={s.size} />
</GridItem>
<GridItem>
<SecurityIcon size={s.size} />
</GridItem>
<GridItem>
<VerifiedIcon size={s.size} />
</GridItem>
</Fragment>
))}
</Grid>
);
}

function GridItem({ children }: { children: React.ReactNode }) {
return <Item className="justify-self-center text-muted">{children}</Item>;
}
1 change: 1 addition & 0 deletions apps/site/src/components/code/components/demos/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { IconSizesDemo } from './icon/icon-sizes.demo';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { clsx } from 'clsx';

export function Body({ className, children }: { children: React.ReactNode; className?: string }) {
return <div className={clsx('typography-body-10 [&_p]:mb-2', className)}>{children}</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Hr() {
return <hr className="my-4 border-0 border-t border-border" />;
}
2 changes: 2 additions & 0 deletions apps/site/src/components/code/components/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Hr } from './hr.component';
export { Body } from './body.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client';

import { Grid, Item } from '@westpac/ui';
import { type BrandKey } from '@westpac/ui/tailwind';
import { useSearchParams } from 'next/navigation';

import { Svg } from '@/components/svg';

import { getColorPalette } from './colors.utils';

export function Colors({ palette }: { palette: string }) {
const searchParams = useSearchParams();
const brand = (searchParams.get('brand') ?? 'wbc') as BrandKey;
const colorPalette = getColorPalette({ brand, palette });
return (
<Grid tag="ul" className="mt-2">
{colorPalette.map(color => (
<Item key={color.name} tag="li" span={{ intial: 12, xsl: 6, sm: 4, md: 3 }}>
<div className="flex flex-row items-center bg-white p-4 xsl:flex-col xsl:items-stretch">
<Svg viewBox="0 0 132 132" width={132} height={132}>
<circle fill={color.hex} cx="66" cy="66" r="66" />
</Svg>
<div className="typography-body-10 ml-4 flex flex-col xsl:ml-0 xsl:mt-2 xsl:px-2">
<strong className="mb-0.5">{color.name}</strong>
<span className="mb-0.5">{color.hex}</span>
<span>{color.rgb}</span>
</div>
</div>
</Item>
))}
</Grid>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { type BrandKey, type ColorsKey } from '@westpac/ui/tailwind';

export const PRIMARY_COLORS: ColorsKey[] = [
'primary',
'hero',
'neutral',
'heading',
'text',
'muted',
'border',
'background',
'light',
];

export const SECONDARY_COLORS: Record<BrandKey, Record<string, string>> = {
wbc: {
'Bright Purple': '#991AD6',
'Bright Pink': '#FF3DDB',
'Dark Red': '#990000',
'Light Grey': '#E8E8ED',
'Light Purple': '#E0BAF2',
'Light Pink': '#FFD9F7',
},
bom: {
'Dark Purple': '#20024E',
'Mid Purple': '#685AC0',
'Light Purple': '#A094FC',
},
stg: {
Sky: '#3FC3EB',
Plum: '#502D79',
Amber: '#FF7F29',
'St.George Green': '#78BE20',
'St.George Yellow': '#FFCD00',
},
btfg: {
'BT Blue': '#00AFD7',
'BT Black': '#2C2A29',
'BT Steel': '#80A1B6',
},
bsa: {
Deep: '#00204F',
Bight: '#00ADBD',
Gum: '#5CBB3E',
Grape: '#A22269',
Sky: '#ABE2EC',
Outback: '#F7921E',
},
wbg: {
Red: '#DA1710',
Croral: '#FF7468',
Pink: '#F9C1CF',
Purple: '#685AC0',
Lilac: '#A094FC',
Aqua: '#84DCE0',
Navy: '#002F6C',
Cobalt: '#376EE2',
Cyan: '#009ED4',
Green: '#78BE20',
Pine: '#002B14',
Lime: '#D4DE25',
},
rams: {
'RAMS Blue': '#0092CD',
'RAMS Green': '#78C339',
'Mid Blue': '#0BC2FF',
'Light Blue': '#86E1FF',
'Light Green': '#B7E096',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NotEditable, component, fields } from '@keystatic/core';

export const colors = component({
preview: props => (
<NotEditable>
<div>{`${props.fields.palette.value} color palette`}</div>
</NotEditable>
),
label: 'Colors',
schema: {
palette: fields.select({
label: 'Color palette',
options: [
{ label: 'Primary', value: 'primary' },
{ label: 'Secondary', value: 'secondary' },
{ label: 'Reserved', value: 'reserved' },
],
defaultValue: 'primary',
}),
},
});
41 changes: 41 additions & 0 deletions apps/site/src/components/component-blocks/colors/colors.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { type BrandConfig, type BrandKey } from '@westpac/ui/tailwind';
import { ALL_THEMES } from '@westpac/ui/themes';
import { BASE_COLORS } from '@westpac/ui/themes-constants';

import { PRIMARY_COLORS, SECONDARY_COLORS } from './colors.constants';

const hexToRgb = (hex: string) =>
hex
?.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (_m, r, g, b) => '#' + r + r + g + g + b + b)
?.substring(1)
?.match(/.{2}/g)
?.map(x => parseInt(x, 16));

export function getColorPalette({ brand, palette }: { brand: BrandKey; palette: string }) {
const colorPalette: { hex: string; name: string; rgb: string }[] = [];
if (palette === 'primary') {
const theme = ALL_THEMES.find((brandTheme: BrandConfig) => brandTheme.code === brand.toUpperCase())?.colors;
if (theme) {
PRIMARY_COLORS.forEach(color => {
const hex = theme[color].DEFAULT;
const rgb = hexToRgb(hex) || [];
colorPalette.push({
name: color[0].toUpperCase() + color.slice(1),
hex,
rgb: `R:${rgb[0]} G:${rgb[1]} B:${rgb[2]}`,
});
});
}
} else if (palette === 'secondary') {
Object.entries(SECONDARY_COLORS[brand]).forEach(([name, hex]) => {
const rgb = hexToRgb(hex) || [];
colorPalette.push({ name, hex, rgb: `R:${rgb[0]} G:${rgb[1]} B:${rgb[2]}` });
});
} else if (palette === 'reserved') {
Object.entries(BASE_COLORS).forEach(([key, hex]) => {
const rgb = hexToRgb(hex) || [];
colorPalette.push({ name: key[0].toUpperCase() + key.slice(1), hex, rgb: `R:${rgb[0]} G:${rgb[1]} B:${rgb[2]}` });
});
}
return colorPalette;
}
Loading

0 comments on commit efd8def

Please sign in to comment.