diff --git a/.changeset/tall-birds-sort.md b/.changeset/tall-birds-sort.md new file mode 100644 index 00000000..6ef76dc3 --- /dev/null +++ b/.changeset/tall-birds-sort.md @@ -0,0 +1,5 @@ +--- +"basehub": patch +--- + +New RichText Renderer for React. diff --git a/internal/eslint-config-custom/package.json b/internal/eslint-config-custom/package.json index b29944e7..a3ac5055 100644 --- a/internal/eslint-config-custom/package.json +++ b/internal/eslint-config-custom/package.json @@ -4,15 +4,15 @@ "main": "index.js", "private": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", - "eslint": "^7.23.0", - "eslint-config-next": "^12.0.8", - "eslint-config-prettier": "^8.3.0", - "eslint-import-resolver-typescript": "^2.5.0", - "eslint-plugin-import": "2.25.4", - "eslint-plugin-react": "7.31.8", - "eslint-config-turbo": "latest" + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", + "eslint": "^8.52.0", + "eslint-config-next": "^13.5.6", + "eslint-config-prettier": "^9.0.0", + "eslint-config-turbo": "latest", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "2.29.0", + "eslint-plugin-react": "7.33.2" }, "publishConfig": { "access": "public" diff --git a/internal/tsconfig/react-library.json b/internal/tsconfig/react-library.json index 9c7669ce..82adf3d9 100644 --- a/internal/tsconfig/react-library.json +++ b/internal/tsconfig/react-library.json @@ -7,6 +7,8 @@ "lib": ["dom", "ES2015"], "module": "ESNext", "target": "es6", - "strict": true + "strict": true, + "alwaysStrict": true, + "strictNullChecks": true } } diff --git a/package.json b/package.json index ebf9c9b0..ee8e0e86 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,10 @@ "devDependencies": { "@changesets/cli": "^2.22.0", "eslint-config-custom": "workspace:*", - "prettier": "2.5.1", + "prettier": "3.0.3", "turbo": "1.10.12", "type-fest": "^3.0.0", - "typescript": "^4.7.4" + "typescript": "^5.2.2" }, "packageManager": "pnpm@8.6.12" } diff --git a/packages/basehub/src/bin/main.ts b/packages/basehub/src/bin/main.ts index 6e5d6c00..3c420006 100644 --- a/packages/basehub/src/bin/main.ts +++ b/packages/basehub/src/bin/main.ts @@ -8,7 +8,7 @@ import { runtime__getStuffFromEnvString, } from "./util/get-stuff-from-env"; -export const main = async (args: Args) => { +export const main = async (_args: Args) => { console.log("🪄 Generating..."); const { url, headers } = getStuffFromEnv(); @@ -99,6 +99,8 @@ export const main = async (args: Args) => { }; const basehubExport = ` +import { createFetcher } from "./runtime"; + // we limit options to only the ones we want to expose. type Options = Omit @@ -118,5 +120,14 @@ type Options = Omit 'Query' * */ -export const basehub = (options?: Options) => createClient(options) +export const basehub = (options?: Options) => { + const { url, headers } = getStuffFromEnv(); + + return { + ...createClient(options), + raw: createFetcher({ ...options, url, headers }) as ( + gql: GraphqlOperation + ) => Promise, + }; +}; `; diff --git a/packages/basehub/src/react/rich-text.tsx b/packages/basehub/src/react/rich-text.tsx deleted file mode 100644 index f9dcb047..00000000 --- a/packages/basehub/src/react/rich-text.tsx +++ /dev/null @@ -1,13 +0,0 @@ -type RenderBaseHubRichTextProps = { - html: string; -}; - -export const RenderBaseHubRichText = ({ html }: RenderBaseHubRichTextProps) => { - return ( -
- ); -}; diff --git a/packages/basehub/src/react/rich-text/index.tsx b/packages/basehub/src/react/rich-text/index.tsx new file mode 100644 index 00000000..b1adde68 --- /dev/null +++ b/packages/basehub/src/react/rich-text/index.tsx @@ -0,0 +1,492 @@ +/* eslint-disable jsx-a11y/alt-text */ +/* eslint-disable @next/next/no-img-element */ +import * as React from "react"; + +/** + * TODOs + * + * - similar api to react-markdown + * - support json, html, markdown, plain text? we'd need to see the impact on bundle size + * - support custom blocks (with types) + */ + +// type Formats = +// | { type: "json"; children: unknown } +// | { type: "html" | "markdown" | "plain-text"; children: string }; +type Formats = { children: unknown }; // only json supported for now. + +interface Attrs { + readonly [attr: string]: any; +} + +type Mark = + | { type: "bold" | "italic" | "underline" | "strike" } + | { type: "code"; attrs: { isInline?: boolean } } + | { type: "link"; attrs: { href: string; target: string; class: string } }; + +type Marks = Array; + +type Node = + | { + type: + | "paragraph" + | "bulletList" + | "listItem" + | "taskList" + | "blockquote" + | "codeBlock" + | "table" + | "tableRow"; + attrs?: Attrs; + marks?: Array; + content?: Array; + } + | { + type: "text"; + text: string; + attrs?: Attrs; + marks?: Array; + content?: Array; + } + | { + type: "orderedList"; + attrs?: { start: number }; + marks?: Array; + content?: Array; + } + | { + type: "taskItem"; + attrs?: { checked: boolean }; + marks?: Array; + content?: Array; + } + | { + type: "heading"; + attrs: { level: number }; + marks?: Array; + content?: Array; + } + | { + type: "horizontalRule"; + content?: Array; + } + | { + type: "image"; + attrs: { src: string; alt?: string; width?: number; height?: number }; + marks?: Array; + content?: Array; + } + | { + type: "video"; + attrs: { src: string; width?: number; height?: number }; + marks?: Array; + content?: Array; + } + | { + type: "tableCell" | "tableHeader" | "tableFooter"; + attrs: { colspan: number; rowspan: number }; + marks?: Array; + content?: Array; + } + | { + type: "basehub-block"; + attrs: { id: string }; + marks?: Array; + content?: Array; + }; + +type Handlers = { + p: (props: { children?: React.ReactNode }) => React.ReactElement; + b: (props: { children?: React.ReactNode }) => React.ReactElement; + em: (props: { children?: React.ReactNode }) => React.ReactElement; + s: (props: { children?: React.ReactNode }) => React.ReactElement; + code: (props: { + children?: React.ReactNode; + isInline: boolean; + }) => React.ReactElement; + a: (props: { + children?: React.ReactNode; + href: string; + }) => React.ReactElement; + ol: (props: { children?: React.ReactNode }) => React.ReactElement; + ul: (props: { + children?: React.ReactNode; + isTasksList: boolean; + }) => React.ReactElement; + li: ( + props: { + children?: React.ReactNode; + } & ({ isTaskListItem: false } | { isTaskListItem: true; checked: boolean }) + ) => React.ReactElement; + h1: (props: { children?: React.ReactNode }) => React.ReactElement; + h2: (props: { children?: React.ReactNode }) => React.ReactElement; + h3: (props: { children?: React.ReactNode }) => React.ReactElement; + h4: (props: { children?: React.ReactNode }) => React.ReactElement; + h5: (props: { children?: React.ReactNode }) => React.ReactElement; + h6: (props: { children?: React.ReactNode }) => React.ReactElement; + hr: () => React.ReactElement; + img: (props: { + children?: React.ReactNode; + src: string; + alt?: string; + width?: number; + height?: number; + }) => React.ReactElement; + video: (props: { + children?: React.ReactNode; + src: string; + width?: number; + height?: number; + }) => React.ReactElement; + blockquote: (props: { children?: React.ReactNode }) => React.ReactElement; + pre: (props: { children?: React.ReactNode }) => React.ReactElement; + table: (props: { children?: React.ReactNode }) => React.ReactElement; + tr: (props: { children?: React.ReactNode }) => React.ReactElement; + td: (props: { + children?: React.ReactNode; + colspan: number; + rowspan: number; + }) => React.ReactElement; + th: (props: { + children?: React.ReactNode; + colspan: number; + rowspan: number; + }) => React.ReactElement; + + // todo etc... +}; + +type ExtractPropsForHandler< + Handler extends (props: any) => React.ReactElement, +> = Parameters[0]; + +type CustomBlockBase = { readonly __typename: string }; + +type HandlerMapping< + Blocks extends readonly CustomBlockBase[] = readonly any[], +> = { + [K in Blocks[number]["__typename"]]: ( + props: Extract + ) => React.ReactElement; +}; + +export type RichTextProps< + CustomBlocks extends readonly CustomBlockBase[] = readonly any[], +> = Formats & { + blocks?: CustomBlocks; + components?: Partial>; +}; + +export const RichText = < + CustomBlocks extends readonly CustomBlockBase[] = readonly any[], +>( + props: RichTextProps +) => { + const value = props.children as Node[]; + return value.map((node, index) => { + return ( + + ); + }); +}; + +const defaultHandlers: Handlers = { + a: ({ children, href }) => {children}, + p: ({ children }) =>

{children}

, + b: ({ children }) => {children}, + em: ({ children }) => {children}, + s: ({ children }) => {children}, + code: ({ children }) => {children}, + ol: ({ children }) =>
    {children}
, + ul: ({ children }) =>
    {children}
, + li: ({ children, ...rest }) => { + return ( +
  • + {rest.isTaskListItem ? ( + + ) : null} + {children} +
  • + ); + }, + h1: ({ children }) =>

    {children}

    , + h2: ({ children }) =>

    {children}

    , + h3: ({ children }) =>

    {children}

    , + h4: ({ children }) =>

    {children}

    , + h5: ({ children }) =>
    {children}
    , + h6: ({ children }) =>
    {children}
    , + hr: () =>
    , + img: (props) => , + video: (props) =>