diff --git a/packages/docusaurus-remote-content/CHANGELOG.md b/packages/docusaurus-remote-content/CHANGELOG.md new file mode 100644 index 0000000..420e6f2 --- /dev/null +++ b/packages/docusaurus-remote-content/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log diff --git a/packages/docusaurus-remote-content/README.md b/packages/docusaurus-remote-content/README.md new file mode 100644 index 0000000..1263c98 --- /dev/null +++ b/packages/docusaurus-remote-content/README.md @@ -0,0 +1,3 @@ +# Docusaurus Local Search + +TODO. diff --git a/packages/docusaurus-remote-content/package.json b/packages/docusaurus-remote-content/package.json new file mode 100644 index 0000000..83d9bb1 --- /dev/null +++ b/packages/docusaurus-remote-content/package.json @@ -0,0 +1,39 @@ +{ + "name": "@acid-info/docusaurus-remote-content", + "version": "1.0.0-alpha.0", + "description": "Docusaurus remote content", + "main": "lib/index.js", + "types": "src/plugin.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/acid-info/logos-docusaurus-plugins.git", + "directory": "packages/docusaurus-remote-content" + }, + "license": "MIT", + "scripts": { + "build": "tsc --build", + "watch": "tsc --build --watch", + "prepublishOnly": "yarn build" + }, + "dependencies": { + "axios": "^1.6.2", + "fast-glob": "^3.3.2", + "lodash": "^4.17.21", + "tmp": "^0.2.1", + "unzipper": "^0.10.14" + }, + "engines": { + "node": ">=16.14" + }, + "devDependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/module-type-aliases": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", + "@types/lodash": "^4.14.186", + "@types/tmp": "^0.2.6", + "@types/unzipper": "^0.10.9" + } +} diff --git a/packages/docusaurus-remote-content/src/deps.d.ts b/packages/docusaurus-remote-content/src/deps.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/docusaurus-remote-content/src/index.ts b/packages/docusaurus-remote-content/src/index.ts new file mode 100644 index 0000000..bbf46d7 --- /dev/null +++ b/packages/docusaurus-remote-content/src/index.ts @@ -0,0 +1,169 @@ +import logger from '@docusaurus/logger' +import type { LoadContext, Plugin } from '@docusaurus/types' +import axios from 'axios' +import * as fg from 'fast-glob' +import * as fs from 'fs' +import * as fsp from 'fs/promises' +import * as path from 'path' +import * as tmp from 'tmp' +import unzipper from 'unzipper' + +const copyFile = async (src: string, dest: string) => { + const dirname = path.dirname(dest) + if (!fs.existsSync(dirname)) { + await fsp.mkdir(dirname, { recursive: true }) + } + + await fsp.copyFile(src, dest) +} + +const copyContent = async ( + src: string, + dest: string, + options?: { + keep?: string[] + exclude?: string[] + }, +) => { + const { keep = [], exclude = [] } = options || {} + + const tmpDir = tmp.dirSync({ unsafeCleanup: true }) + + if (!fs.existsSync(dest)) await fsp.mkdir(dest, { recursive: true }) + + { + const filenames = fg.sync(['**/*'], { + cwd: src, + ignore: exclude, + absolute: false, + }) + + for (const filename of filenames) { + const absPath = path.join(src, filename) + const stat = await fsp.stat(absPath) + + if (stat.isDirectory()) { + await fsp.mkdir(path.join(tmpDir.name, filename), { + recursive: true, + }) + } else await copyFile(absPath, path.join(tmpDir.name, filename)) + } + } + + { + const filenames = fg.sync(keep, { + cwd: dest, + }) + + for (const filename of filenames) { + const absPath = path.join(dest, filename) + const stat = await fsp.stat(absPath) + + if (stat.isDirectory()) { + await fsp.mkdir(path.join(tmpDir.name, filename), { + recursive: true, + }) + } else await copyFile(absPath, path.join(tmpDir.name, filename)) + } + } + + await fsp.rm(dest, { recursive: true, force: true }) + await fsp.cp(tmpDir.name, dest, { recursive: true }) + + tmpDir.removeCallback() +} + +type PluginOptions = { + remote: { + type: 'zip' + url: string + dir?: string + } + + // The directory in the remote repository containing the content to be copied + contentDir: string + + // The directory in the local site to copy the content to + outDir: string + + // Exclude files matching these glob patterns + exclude?: string[] + + // Keep local files matching these glob patterns + keep?: string[] + + // Keep local static files matching these glob patterns + keepStatic?: string[] +} + +export default async function remoteContentPlugin( + context: LoadContext, + options: PluginOptions, +): Promise> { + const tempDir = tmp.dirSync({ unsafeCleanup: true }) + const repoDir = path.join(tempDir.name, 'repo') + const zipDir = path.join(tempDir.name, 'zip') + + const downloadRemoteContent = async () => { + const { remote, contentDir, outDir, exclude = [], keep = [] } = options + + if (remote.type === 'zip') { + const zip = await axios + .get(remote.url, { responseType: 'stream' }) + .then((res) => res.data) + const dest = unzipper.Extract({ path: zipDir }) + + await new Promise((resolve, reject) => { + zip.pipe(dest) + + dest.on('close', resolve) + dest.on('error', reject) + }) + + if (remote.dir) { + await fsp.rename(path.join(zipDir, remote.dir), repoDir) + } else await fsp.rename(zipDir, repoDir) + } + } + + const copyRemoteContent = async () => { + const { remote, contentDir, outDir, exclude = [], keep = [] } = options + + await copyContent( + path.join(repoDir, contentDir), + path.join(context.siteDir, outDir), + { + exclude, + keep, + }, + ) + + await copyContent( + path.join(repoDir, 'static'), + path.join(context.siteDir, 'static'), + { + exclude: [], + keep: options.keepStatic ?? [], + }, + ) + } + + try { + logger.info`Downloading remote content from ${options.remote.url}` + await downloadRemoteContent() + await copyRemoteContent() + tempDir.removeCallback() + } catch (error) { + tempDir.removeCallback() + console.error(error) + logger.error`Failed to download remote content from ${options.remote.url}` + + process.exit(1) + } + + return { + name: 'docusaurus-remote-content', + } +} + +export { type PluginOptions } diff --git a/packages/docusaurus-remote-content/src/plugin.d.ts b/packages/docusaurus-remote-content/src/plugin.d.ts new file mode 100644 index 0000000..ccd487e --- /dev/null +++ b/packages/docusaurus-remote-content/src/plugin.d.ts @@ -0,0 +1 @@ +export type * from './index' diff --git a/packages/docusaurus-remote-content/tsconfig.json b/packages/docusaurus-remote-content/tsconfig.json new file mode 100644 index 0000000..af48de9 --- /dev/null +++ b/packages/docusaurus-remote-content/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": false, + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "rootDir": "src", + "outDir": "lib", + "lib": ["DOM"] + }, + "include": ["src"], + "exclude": ["src/theme", "**/__tests__/**"] +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/README.md b/packages/logos-docusaurus-brand-guidelines-theme/README.md new file mode 100644 index 0000000..89c5277 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/README.md @@ -0,0 +1 @@ +# Logos Docusaurus Brand Guidelines Theme diff --git a/packages/logos-docusaurus-brand-guidelines-theme/gulpfile.js b/packages/logos-docusaurus-brand-guidelines-theme/gulpfile.js new file mode 100644 index 0000000..d7b3f88 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/gulpfile.js @@ -0,0 +1,88 @@ +const _ = require('lodash') + +const gulp = require('gulp') +const path = require('path') +const merge = require('merge2') +const rimraf = require('rimraf') +const ts = require('gulp-typescript') +const TscWatch = require('tsc-watch/client') +const sourcemaps = require('gulp-sourcemaps') +const syncDirectory = require('sync-directory') +const { replaceTscAliasPaths } = require('tsc-alias') + +const project = ts.createProject('./tsconfig.client.json', { + declaration: true, + isolatedModules: false, +}) + +const SOURCE_DIR = project.config.compilerOptions.rootDir ?? 'src' +const OUT_DIR = project.config.compilerOptions.outDir ?? 'lib' + +const sourceDir = path.resolve('./', SOURCE_DIR) +const outDir = path.resolve('./', OUT_DIR) + +const clean = async (cb) => { + rimraf(outDir, cb) +} + +const build = (cb) => { + return gulp.series(buildClient, copyFiles, postBuild, buildServer)(cb) +} + +const buildClient = () => { + const compiled = project.src().pipe(sourcemaps.init()).pipe(project()) + + return merge(compiled.dts, compiled.js.pipe(sourcemaps.write())).pipe( + gulp.dest(outDir), + ) +} + +const replaceTsAliasPaths = () => + replaceTscAliasPaths({ + configFile: './tsconfig.client.json', + }) + +const postBuild = (done) => + gulp.series((cb) => replaceTsAliasPaths().finally(cb))(done) + +const buildServer = () => { + const project = ts.createProject('./tsconfig.json', { + isolatedModules: false, + }) + + const compiled = project.src().pipe(project()) + + return merge(compiled.dts, compiled.js).pipe(gulp.dest(outDir)) +} + +const syncDirectories = async (watch = false) => { + syncDirectory.async(sourceDir, outDir, { + watch, + verbose: 1, + exclude: [/.*\.(ts|tsx)$/], + }) +} + +const watch = async () => { + syncDirectories(true) + + const watch = new TscWatch() + + watch.on('success', async () => { + await postBuild() + }) + + watch.start('--build') +} + +const copyFiles = (cb) => { + syncDirectories(false).finally(cb) +} + +gulp.task('build', build) +gulp.task('watch', watch) +gulp.task('clean', clean) +gulp.task('build-server', buildServer) +gulp.task('build-client', buildClient) +gulp.task('post-build', postBuild) +gulp.task('copy-files', copyFiles) diff --git a/packages/logos-docusaurus-brand-guidelines-theme/package.json b/packages/logos-docusaurus-brand-guidelines-theme/package.json new file mode 100644 index 0000000..de3420b --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/package.json @@ -0,0 +1,43 @@ +{ + "name": "@acid-info/logos-docusaurus-brand-guidelines-theme", + "version": "1.0.0-alpha.0", + "description": "", + "main": "lib/index.js", + "types": "src/theme.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/acid-info/logos-docusaurus-plugins.git", + "directory": "packages/logos-docusaurus-brand-guidelines-theme" + }, + "license": "MIT", + "scripts": { + "clean": "yarn gulp clean", + "prebuild": "yarn clean", + "build": "yarn gulp build", + "watch": "yarn gulp watch", + "build:client": "yarn gulp build-client", + "build:server": "yarn gulp build-server", + "prepublishOnly": "yarn clean && yarn build" + }, + "dependencies": {}, + "devDependencies": { + "@acid-info/logos-docusaurus-theme": "1.0.0-alpha.121", + "@types/lodash": "^4.14.186", + "@types/mdx-js__react": "^1.5.5", + "@types/three": "^0.152.1", + "glob": "^10.3.10", + "react-docgen": "^7.0.0", + "react-docgen-markdown-renderer": "^2.1.3", + "sass": "^1.55.0", + "tsc-alias": "^1.7.0", + "tsc-watch": "^5.0.3" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0", + "@acid-info/logos-docusaurus-theme": "1.0.0-alpha.121" + }, + "engines": { + "node": ">=16.14" + } +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/ColorCard.module.scss b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/ColorCard.module.scss new file mode 100644 index 0000000..24583bc --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/ColorCard.module.scss @@ -0,0 +1,28 @@ +.root { + width: 100px; + border: 1px solid rgb(var(--lsd-border-primary)); +} + +.root.fullWidth { + width: 100%; +} + +.root .color { + width: 100%; + height: 130px; + border-bottom: 1px solid rgb(var(--lsd-border-primary)); +} + +.root .info { + padding: 16px; +} + +.root .title { + margin-bottom: 16px; +} + +.root .variables { + display: grid; + gap: 8px 16px; + grid-template: auto / auto 1fr; +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/ColorCard.tsx b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/ColorCard.tsx new file mode 100644 index 0000000..325fa18 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/ColorCard.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import styles from './ColorCard.module.scss' +import clsx from 'clsx' +import { Typography } from '@acid-info/lsd-react' + +export type ColorCardProps = Omit< + React.HTMLProps, + 'title' | 'color' +> & { + color: string + title: React.ReactNode + fullWidth?: boolean + variables?: { + name: string + value: string + }[] +} + +export const ColorCard: React.FC = ({ + title, + color, + fullWidth, + variables = [], + ...props +}) => { + return ( +
+
+
+ + {title} + +
+ {variables.map((variable, index) => ( + + {variable.name} + {variable.value} + + ))} +
+
+
+ ) +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/index.ts b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/index.ts new file mode 100644 index 0000000..66eddf2 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ColorCard/index.ts @@ -0,0 +1 @@ +export * from './ColorCard' diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/ComponentCard.module.scss b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/ComponentCard.module.scss new file mode 100644 index 0000000..c484d17 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/ComponentCard.module.scss @@ -0,0 +1,42 @@ +.root { + width: 100%; + height: auto; + border: 1px solid rgb(var(--lsd-border-primary)); + display: flex; + flex-direction: column; + position: relative; + text-decoration: none; + text-decoration: none !important; + + aspect-ratio: 1 / 1; +} + +.root .title { + padding: 16px; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; +} + +.root .image { +} + +.root .imageContainer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 0 48px; + display: flex; + align-items: center; + justify-content: center; + + img { + width: auto; + height: auto; + object-fit: cover; + user-select: none; + } +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/ComponentCard.tsx b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/ComponentCard.tsx new file mode 100644 index 0000000..503fd45 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/ComponentCard.tsx @@ -0,0 +1,41 @@ +import { Typography } from '@acid-info/lsd-react' +import ThemedImage from '@theme/ThemedImage' +import clsx from 'clsx' +import React from 'react' +import styles from './ComponentCard.module.scss' +import Link, { Props as LinkProps } from '@docusaurus/Link' + +export type ComponentCardProps = Omit & { + title: React.ReactNode + imageSrc?: string + imageDarkSrc?: string + imageWidth?: string | number + imageHeight?: string | number +} + +export const ComponentCard: React.FC = ({ + title, + imageSrc, + imageDarkSrc, + imageWidth, + imageHeight, + ...props +}) => { + return ( + + + {title} + +
+ +
+ + ) +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/index.ts b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/index.ts new file mode 100644 index 0000000..18313fd --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentCard/index.ts @@ -0,0 +1 @@ +export * from './ComponentCard' diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/ComponentGrid.module.scss b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/ComponentGrid.module.scss new file mode 100644 index 0000000..803aaa2 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/ComponentGrid.module.scss @@ -0,0 +1,24 @@ +@use '@acid-info/logos-docusaurus-theme/lib/client/css/utils'; + +.root { +} + +@include utils.responsive('md', 'up') { + .root .item { + &:nth-child(even) > a { + border-left: none; + } + + &:not(.lastRow) > a { + border-bottom: none; + } + } +} + +@include utils.responsive('md', 'down') { + .root .item { + &:not(:last-child) > a { + border-bottom: none; + } + } +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/ComponentGrid.tsx b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/ComponentGrid.tsx new file mode 100644 index 0000000..0bcd7dd --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/ComponentGrid.tsx @@ -0,0 +1,38 @@ +import { + Grid, + GridProps, +} from '@acid-info/logos-docusaurus-theme/lib/client/components/mdx' +import React from 'react' +import { ComponentCard, ComponentCardProps } from '../ComponentCard' +import styles from './ComponentGrid.module.scss' +import clsx from 'clsx' + +export type ComponentGridProps = GridProps & { + list: ComponentCardProps[] +} + +export const ComponentGrid: React.FC = ({ + list = [], + ...props +}) => { + return ( + + {list.map((props, idx) => ( + = list.length - 2 && styles.lastRow, + )} + > + + + ))} + + ) +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/index.ts b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/index.ts new file mode 100644 index 0000000..b3f9ff8 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ComponentGrid/index.ts @@ -0,0 +1 @@ +export * from './ComponentGrid' diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/Image.module.scss b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/Image.module.scss new file mode 100644 index 0000000..fe5c36e --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/Image.module.scss @@ -0,0 +1,46 @@ +@use '@acid-info/logos-docusaurus-theme/lib/client/css/utils'; + +.overlay { + position: fixed; + top: 0; + left: 0; + + width: 100vw; + height: 100vh; + + background-color: rgb(var(--lsd-theme-secondary)); + display: flex; + justify-content: center; + align-items: center; + z-index: 10000; +} + +.thumbnail { + cursor: pointer; +} + +.enlarged { + position: fixed; + + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + max-height: 90vh; + max-width: 90vw; + + display: block; + object-fit: contain; +} + +.closeButton { + position: fixed; + top: 18px; + right: 16px; + + background-color: rgb(var(--lsd-theme-secondary)) !important; + + cursor: pointer; + + z-index: 1001; +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/Image.tsx b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/Image.tsx new file mode 100644 index 0000000..3701f4e --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/Image.tsx @@ -0,0 +1,116 @@ +import { LightBoxWrapper } from '@acid-info/logos-docusaurus-theme/lib/client/containers/LightBox' +import { CloseIcon, IconButton } from '@acid-info/lsd-react' +import clsx from 'clsx' +import React, { useEffect, useMemo, useState } from 'react' +import styles from './Image.module.scss' + +export type LoadedImage = { + preSrc: string + width: number + height: number + placeholder: any + src: string + srcSet: string + images: { + width: number + height: number + path: string + }[] +} + +export type ImageProps = Omit< + React.ImgHTMLAttributes, + 'src' +> & { + src?: string + img?: LoadedImage | undefined | null + minWidth?: number +} + +export const Image: React.FC = ({ + minWidth = 0, + src, + img, + ...rest +}) => { + const [enlarged, setEnlarged] = useState(false) + const singleImage = !img?.images || img.images.length === 0 + + const originalImage = { + src: src, + width: rest.width, + height: rest.height, + } + + const images = img?.images || [] + + const sorted = useMemo( + () => + [...images] + .sort((a, b) => a.width - b.width) + .filter((img) => img.width >= minWidth), + [images], + ) + + const [smallest] = [sorted[0] ?? originalImage] + .map( + (img) => + img && { + width: img.width, + height: img.height, + src: 'src' in img ? img.src : 'path' in img ? img.path : '', + }, + ) + .filter((img) => !!img) + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setEnlarged(false) + } + } + + document.addEventListener('keydown', handleKeyDown) + + return () => { + document.removeEventListener('keydown', handleKeyDown) + } + }, []) + + const imageElement = ( + !singleImage && setEnlarged(true)} + {...rest} + className={clsx( + styles.image, + !singleImage && styles.thumbnail, + rest.className, + )} + /> + ) + + return ( + <> + {img ? imageElement : {imageElement}} + {enlarged && ( +
+ setEnlarged(false)} + className={styles.closeButton} + > + + + +
+ )} + + ) +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/index.ts b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/index.ts new file mode 100644 index 0000000..072b161 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/Image/index.ts @@ -0,0 +1 @@ +export * from './Image' diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/ImageGrid.module.scss b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/ImageGrid.module.scss new file mode 100644 index 0000000..bf6269a --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/ImageGrid.module.scss @@ -0,0 +1,61 @@ +@use '@acid-info/logos-docusaurus-theme/lib/client/css/utils'; + +.thumbnailImageContainer { + display: flex; + justify-content: center; + align-items: center; + + height: 100%; +} + +.thumbnailImage { + width: 100%; + height: auto; + object-fit: cover; +} + +.masonry .thumbnailImageContainer { + flex-direction: column; + margin-bottom: 16px; + height: auto; // overrides the height: 100% from the default thumbnailImageContainer +} + +.squareThumbnails .thumbnailImageContainer { + aspect-ratio: 1 / 1; +} + +// Overlay and expanded image styles. +.overlay { + position: fixed; + top: 0; + left: 0; + + width: 100vw; + height: 100vh; + + background-color: rgb(var(--lsd-theme-secondary)); + display: flex; + justify-content: center; + align-items: center; + z-index: 10000; +} + +.closeButton { + position: fixed; + top: 18px; + right: 16px; + + background-color: rgb(var(--lsd-theme-secondary)) !important; + + cursor: pointer; + + z-index: 1001; +} + +// Single column image galleries on mobile don't have spacing between them. +// The following padding bottom fixes that. +@include utils.responsive('md', 'down') { + .thumbnailImageContainer { + padding-bottom: 16px; + } +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/ImageGrid.tsx b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/ImageGrid.tsx new file mode 100644 index 0000000..9c2cb80 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/ImageGrid.tsx @@ -0,0 +1,104 @@ +import { + Grid, + GridProps, +} from '@acid-info/logos-docusaurus-theme/lib/client/components/mdx' +import clsx from 'clsx' +import React from 'react' +import { Image, LoadedImage } from '../Image/Image' +import styles from './ImageGrid.module.scss' + +type ImageType = { + img?: { src: LoadedImage } + alt?: string + src: string +} + +type MasonryModeProps = { + firstColumnSize: number + images: ImageType[] +} + +// Masonry mode only has 2 columns with 1 item each. +const MasonryMode: React.FC = ({ + firstColumnSize, + images, +}) => { + const firstColumnImages = images.slice(0, firstColumnSize) + const secondColumnImages = images.slice(firstColumnSize) + + return ( + <> + + {firstColumnImages.map((image, index) => ( +
+ {image.alt +
+ ))} +
+ + {secondColumnImages.map((image, index) => ( +
+ {image.alt +
+ ))} +
+ + ) +} + +export type ImageGridProps = GridProps & { + images: ImageType[] + mode?: 'masonry' | 'regular' | 'square-thumbnails' + firstColumnSize?: number +} + +export const ImageGrid: React.FC = ({ + mode = 'regular', + images, + firstColumnSize = 0, + ...props +}) => { + return ( + <> + + {mode === 'masonry' ? ( + + ) : ( + images.map((image, index) => ( + + {image.alt + + )) + )} + + + ) +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/index.ts b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/index.ts new file mode 100644 index 0000000..46b8437 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/ImageGrid/index.ts @@ -0,0 +1 @@ +export * from './ImageGrid' diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/StorybookDemo/StoryBookDemo.module.scss b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/StorybookDemo/StoryBookDemo.module.scss new file mode 100644 index 0000000..9c8ad66 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/StorybookDemo/StoryBookDemo.module.scss @@ -0,0 +1,13 @@ +.root { +} + +.iframeContainer { + width: 100%; + margin-top: 8px; + + iframe { + transform: translateY(-20px); + width: 100%; + background-color: transparent; + } +} diff --git a/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/StorybookDemo/StorybookDemo.tsx b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/StorybookDemo/StorybookDemo.tsx new file mode 100644 index 0000000..a94f734 --- /dev/null +++ b/packages/logos-docusaurus-brand-guidelines-theme/src/client/components/mdx/StorybookDemo/StorybookDemo.tsx @@ -0,0 +1,184 @@ +import { Dropdown } from '@acid-info/lsd-react' +import { useColorMode } from '@docusaurus/theme-common' +import clsx from 'clsx' +import React, { useMemo, useRef, useState } from 'react' +import styles from './StoryBookDemo.module.scss' + +const onIframeLoad = (iframe: HTMLIFrameElement) => { + const handleIframeMessage = (event: MessageEvent) => { + if ( + typeof event.data === 'string' && + event.data.includes('storyRendered') + ) { + // XXX: This if() block executes when storybook has finished rendering. + // So if we ever want to implement a loading screen, this if() block may help. + requestHeightFromIframe() + } else if (event.data && event.data.type === 'iframeHeightResponse') { + iframe.style.height = `${event.data.height}px` + } + } + + // Request height from the iframe + const requestHeightFromIframe = () => { + if (!iframe || !iframe.contentWindow) { + return + } + + iframe.contentWindow.postMessage( + { + type: 'requestHeight', + }, + '*', + ) + } + + window.addEventListener('message', handleIframeMessage) + + // The following setInterval is just a safety mechanism for the very unlikely case + // of the iframe not sending a storyRendered message. + setInterval(() => { + requestHeightFromIframe() + }, 1000) +} + +type GlobalType = { + name: string + description: string + defaultValue: string + toolbar: { + icon: string + items: { title: string; value: string }[] + } +} + +type ComponentProperty = { + name: string + type: { + name: 'enum' + value: string[] + } + defaultValue?: string +} + +export type GlobalControls = 'themeFont' | 'themeColor' + +export type StorybookDemoProps = { + name: string + docId: string + storyId: string + storybookUrl: string + globalTypes: Record + componentProperties: ComponentProperty[] + globalControls?: GlobalControls[] +} + +export const StorybookDemo: React.FC = ({ + name, + docId, + storyId, + storybookUrl, + globalTypes, + globalControls = ['themeColor', 'themeFont'], + componentProperties = [], +}) => { + const colorMode = useColorMode() + + const iframeRef = useRef(null) + + const [globalProps, setGlobalProps] = useState( + Object.fromEntries( + Object.entries(globalTypes).map(([name, prop]) => [ + name, + name === 'themeColor' + ? colorMode.colorMode.slice(0, 1).toUpperCase() + + colorMode.colorMode.slice(1) + : prop.defaultValue, + ]), + ), + ) + + const [props, setProps] = useState( + Object.fromEntries( + componentProperties.map((prop) => [prop.name, prop.defaultValue]), + ), + ) + + const embedUrl = useMemo(() => { + const el = iframeRef.current + + const url = el?.src + ? new URL(el.src) + : new URL('/iframe.html', storybookUrl as string) + + url.searchParams.set('id', docId) + storyId && url.searchParams.set('storyId', storyId) + url.searchParams.set('globals', 'themeColor:Dark;themeFont:sans-serif') + url.searchParams.set('embedded', 'true') + url.searchParams.set( + 'hide', + 'title,subtitle,toolbar' + + (storyId ? ',description,canvas-border,code' : ''), + ) + url.searchParams.set( + 'globalControls', + globalControls && globalControls.length + ? globalControls.join(',') + : 'false', + ) + + return url.toString() + }, [docId, storyId, globalProps, globalControls, props]) + + return ( +
+
+ {Object.entries(globalTypes).map(([name, prop]) => ( + + setGlobalProps((state) => ({ ...state, [name]: value as string })) + } + options={prop.toolbar.items.map((i) => ({ + name: i.title, + value: i.value, + }))} + triggerLabel={prop.name} + label={prop.name} + /> + ))} + {componentProperties.map((prop) => ( + + setProps((state) => ({ ...state, [prop.name]: value as string })) + } + options={prop.type.value.map((i) => ({ + name: i, + value: i, + }))} + triggerLabel={prop.name} + label={prop.name} + /> + ))} +
+
+