Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<ComparisonTable>, <FAQ>, <Search> tweaks #1927

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7fe38a4
fix(deps): update @theguild/components + nextra to v4.0.0-app-router.39
renovate[bot] Dec 18, 2024
3b27662
Merge branch 'main' into renovate/@theguildcomponents-+-nextra
dimaMachina Dec 20, 2024
4c9daac
update nextra and move search classes to style.css
dimaMachina Dec 23, 2024
5db65e3
Merge branch 'main' into upd-nextra4
dimaMachina Dec 23, 2024
f9df32f
add faq component
dimaMachina Dec 23, 2024
7023a32
add faq component
dimaMachina Dec 23, 2024
7c43302
add faq component
dimaMachina Dec 23, 2024
0db29d5
add table
dimaMachina Dec 26, 2024
945c465
refactor comparison table
dimaMachina Jan 2, 2025
e103cd6
upd
dimaMachina Jan 3, 2025
88d29fe
Merge branch 'main' into upd-nextra4-2
dimaMachina Jan 3, 2025
5820425
pnpm i
dimaMachina Jan 3, 2025
b63a862
lint
dimaMachina Jan 3, 2025
09bf8a8
chore(dependencies): updated changesets for modified dependencies
github-actions[bot] Jan 3, 2025
f566890
fix build
dimaMachina Jan 3, 2025
08e100b
fix light only pages
dimaMachina Jan 3, 2025
040af40
fix text color
dimaMachina Jan 6, 2025
1060194
polish
dimaMachina Jan 6, 2025
d8069a7
Merge branch 'main' into upd-nextra4-2
dimaMachina Jan 6, 2025
d54bd22
polish
dimaMachina Jan 6, 2025
b4f6927
polish
dimaMachina Jan 6, 2025
eb89005
polish
dimaMachina Jan 6, 2025
cf90315
add remark-link-rewrite.ts
dimaMachina Jan 6, 2025
d0090d9
upd selections style
dimaMachina Jan 6, 2025
d6e8022
chore(dependencies): updated changesets for modified dependencies
github-actions[bot] Jan 6, 2025
fdac057
use satisfies
dimaMachina Jan 6, 2025
ac86875
fix build
dimaMachina Jan 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/@theguild_components-1927-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@theguild/components": patch
---
dependencies updates:
- Added dependency [`@radix-ui/react-accordion@^1.2.2` ↗︎](https://www.npmjs.com/package/@radix-ui/react-accordion/v/1.2.2) (to `dependencies`)
- Added dependency [`@radix-ui/react-icons@^1.3.2` ↗︎](https://www.npmjs.com/package/@radix-ui/react-icons/v/1.3.2) (to `dependencies`)
- Added dependency [`[email protected]` ↗︎](https://www.npmjs.com/package/unist-util-visit/v/5.0.0) (to `dependencies`)
8 changes: 6 additions & 2 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
"dependencies": {
"@giscus/react": "3.1.0",
"@next/bundle-analyzer": "15.1.3",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-navigation-menu": "^1.2.0",
"clsx": "2.1.1",
"fuzzy": "0.1.3",
Expand All @@ -60,13 +62,15 @@
"react-paginate": "8.2.0",
"react-player": "2.16.0",
"semver": "^7.3.8",
"tailwind-merge": "^2.5.2"
"tailwind-merge": "^2.5.2",
"unist-util-visit": "5.0.0"
},
"devDependencies": {
"@svgr/babel-plugin-remove-jsx-attribute": "^8.0.0",
"@svgr/plugin-svgo": "^8.1.0",
"@theguild/editor": "workspace:*",
"@theguild/tailwind-config": "0.6.2",
"@types/dedent": "0.7.2",
"@types/mdast": "4.0.4",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/semver": "7.5.8",
Expand Down
45 changes: 45 additions & 0 deletions packages/components/src/components/comparison-table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ComponentProps, FC } from 'react';
import { cn } from '@theguild/components';

const Table: FC<ComponentProps<'table'>> = ({ className, ...props }) => {
return (
<table
className={cn(
'x:block x:overflow-x-auto nextra-scrollbar overflow-x-auto rounded-2xl border border-green-200',
className,
)}
{...props}
/>
);
};

const TableRow: FC<ComponentProps<'tr'> & { highlight?: boolean }> = ({
highlight,
className,
...props
}) => {
return <tr className={cn(highlight && 'bg-green-100', className)} {...props} />;
};

const cellStyle = cn(
'border border-green-200 p-4 first:border-l-0 last:border-r-0',
'[tbody_&]:border-b-0 [thead_&]:border-t-0',
'first:sticky',
'first:left-0',
'max-sm:first:drop-shadow-2xl',
'first:bg-[rgb(var(--nextra-bg))]',
);

const TableHeader: FC<ComponentProps<'th'>> = ({ className, ...props }) => {
return <th className={cn(cellStyle, 'font-medium', className)} {...props} />;
};

const TableCell: FC<ComponentProps<'td'>> = ({ className, ...props }) => {
return <td className={cn(cellStyle, className)} {...props} />;
};

export const ComparisonTable = Object.assign(Table, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could

import { Table } from "@theguild/components"
Suggested change
export const ComparisonTable = Object.assign(Table, {
export const Table = Object.assign(_Table, {

It doesn't really have to be a comparison table. It is one in Mesh, maybe in Yoga too, but I think the styles work for every table.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in components <Table> component already reexported from Nextra

, should we override the default Table for mdx pages too?

Row: TableRow,
Header: TableHeader,
Cell: TableCell,
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function ExploreMainProductCards({
</Heading>
<TextLink
href={isHive ? '/ecosystem' : 'https://the-guild.dev/graphql/hive/ecosystem'}
className="mt-4 lg:mt-6"
className="mt-4 text-green-800 lg:mt-6"
>
Learn more
<ArrowIcon />
Expand Down
25 changes: 25 additions & 0 deletions packages/components/src/components/faq/attach-page-faq-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

import { FC, useEffect } from 'react';
import { usePathname } from 'next/navigation';

export const AttachPageFAQSchema: FC<{ faqPages: string[] }> = ({ faqPages }) => {
const pathname = usePathname();

useEffect(() => {
const html = document.querySelector('html')!;
const path = pathname.replace('/graphql/hive', '/');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this always be /graphql/hive?


if (faqPages.includes(path) && !html.hasAttribute('itemscope')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I mentioned on Slack, I doubt this check is needed.

html.setAttribute('itemscope', '');
html.setAttribute('itemtype', 'https://schema.org/FAQPage');

return () => {
html.removeAttribute('itemscope');
html.removeAttribute('itemtype');
};
}
}, [pathname]);

Check warning on line 22 in packages/components/src/components/faq/attach-page-faq-schema.ts

View workflow job for this annotation

GitHub Actions / Build

React Hook useEffect has a missing dependency: 'faqPages'. Either include it or remove the dependency array

return null;
};
122 changes: 122 additions & 0 deletions packages/components/src/components/faq/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
Children,
cloneElement,
ComponentProps,
ComponentPropsWithoutRef,
FC,
ReactElement,
ReactNode,
} from 'react';
import * as RadixAccordion from '@radix-ui/react-accordion';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { cn } from '../../cn';
import { Anchor } from '../anchor';
import { Heading } from '../heading';
import { AttachPageFAQSchema } from './attach-page-faq-schema';

const UnwrapChild: FC<{ children?: ReactNode }> = props => props.children;

const a: FC<ComponentPropsWithoutRef<'a'>> = props => (
<Anchor
className="hive-focus rounded underline hover:text-blue-700"
{...props}
href={props.href!}
>
{props.children}
</Anchor>
);

const h2: FC<ComponentPropsWithoutRef<'h2'>> = props => (
<Heading as="h2" size="md" className="basis-1/2" {...props} />
);

export const FrequentlyAskedQuestions: FC<
ComponentProps<typeof AttachPageFAQSchema> & {
className?: string;
children: ReactElement;
}
> = ({ className, faqPages, children }) => {
return (
<section
className={cn(
className,
'flex flex-col gap-x-6 gap-y-2 px-4 py-6 text-green-1000 md:flex-row md:px-10 lg:gap-x-24 lg:px-[120px] lg:py-24',
)}
>
<AttachPageFAQSchema faqPages={faqPages} />
{cloneElement(children, {
components: {
a,
h2,
Comment on lines +47 to +50
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be done with a provider in Server Components world, right?

Copy link
Collaborator Author

@dimaMachina dimaMachina Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, here is code from Yoga how it looks like, FAQ is mdx
image

p: UnwrapChild,
ul: Accordion,
li: AccordionItem,
},
})}
</section>
);
};

const Accordion: FC<ComponentPropsWithoutRef<'ul'>> = props => (
<RadixAccordion.Root asChild type="single" collapsible>
<ul className="basis-1/2 divide-y divide-beige-400 max-xl:grow" {...props} />
</RadixAccordion.Root>
);

const AccordionItem: FC<ComponentPropsWithoutRef<'li'>> = props => {
const texts = Children.toArray(props.children).filter(child => child !== '\n');

if (texts.length === 0) {
return null;
}

if (texts.length < 2) {
// eslint-disable-next-line no-console
console.error(texts);
throw new Error(`Expected a question and an answer, got ${texts.length} items`);
}

const [first, ...answers] = texts;

const question =
typeof first === 'string'
? first
: typeof first === 'object' && 'type' in first
? first.props.children
: null;

if (!question) return null;

return (
<RadixAccordion.Item
asChild
value={question}
className="relative pb-0 focus-within:z-10 data-[state=open]:pb-4"
itemScope
itemProp="mainEntity"
itemType="https://schema.org/Question"
>
<li>
<RadixAccordion.Header>
<RadixAccordion.Trigger className="hive-focus duration-[.8s] -mx-2 my-1 flex w-[calc(100%+1rem)] items-center justify-between rounded-xl bg-white px-2 py-3 text-left font-medium transition-colors hover:bg-beige-100/80 md:my-2 md:py-4">
<span itemProp="name">{question}</span>
<ChevronDownIcon className="size-5 [[data-state='open']_&]:[transform:rotateX(180deg)]" />
</RadixAccordion.Trigger>
</RadixAccordion.Header>
<RadixAccordion.Content
forceMount
className="overflow-hidden bg-white text-green-800 data-[state=closed]:hidden"
itemScope
itemProp="acceptedAnswer"
itemType="https://schema.org/Answer"
>
<div itemProp="text" className="space-y-2">
{answers.map((answer, i) => (
<p key={i}>{answer}</p>
))}
</div>
</RadixAccordion.Content>
</li>
</RadixAccordion.Item>
);
};
15 changes: 7 additions & 8 deletions packages/components/src/components/giscus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import { FC } from 'react';
import { useTheme } from 'nextra-theme-docs';
import { default as _Giscus, GiscusProps } from '@giscus/react';
import { default as Giscus_, GiscusProps } from '@giscus/react';

export const Giscus: FC<GiscusProps> = props => {
export const Giscus: FC<
Omit<GiscusProps, 'mapping'> & {
mapping?: GiscusProps['mapping'];
}
> = props => {
const { resolvedTheme } = useTheme();
return (
<>
<br />
<_Giscus theme={resolvedTheme} {...props} />
</>
);
return <Giscus_ theme={resolvedTheme} mapping="pathname" {...props} />;
};
9 changes: 4 additions & 5 deletions packages/components/src/components/hive-navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React, {
FC,
forwardRef,
Fragment,
ReactElement,
ReactNode,
useEffect,
useRef,
Expand Down Expand Up @@ -55,6 +56,7 @@ export type HiveNavigationProps = {
logo?: ReactNode;
navLinks?: { href: string; children: ReactNode }[];
developerMenu: DeveloperMenuProps['developerMenu'];
search?: ReactElement;
searchProps?: ComponentProps<typeof Search>;
};

Expand Down Expand Up @@ -83,7 +85,7 @@ export function HiveNavigation({
},
],
developerMenu,
searchProps,
search = <Search />,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You moved the styles here to a .css file, right?

Copy link
Collaborator Author

@dimaMachina dimaMachina Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, because in Yoga I use <VersionnedSearch> search which passes Pagefind's filters

So I will avoid copy-pasting x100 characters className

}: HiveNavigationProps) {
const containerRef = useRef<HTMLDivElement>(null!);

Expand Down Expand Up @@ -152,10 +154,7 @@ export function HiveNavigation({

{children}

<Search
className="relative ml-4 basis-64 [&_:is(input,kbd)]:text-green-700 dark:[&_:is(input,kbd)]:text-neutral-300 [&_input]:h-12 [&_input]:w-full [&_input]:rounded-lg [&_input]:border [&_input]:border-green-200 [&_input]:bg-white [&_input]:pl-4 [&_input]:pr-8 [&_input]:ring-[hsl(var(--nextra-primary-hue)_var(--nextra-primary-saturation)_32%/var(--tw-ring-opacity))] [&_input]:ring-offset-[rgb(var(--nextra-bg))] dark:[&_input]:border-neutral-800 [&_input]:dark:bg-inherit [&_kbd]:absolute [&_kbd]:right-4 [&_kbd]:top-1/2 [&_kbd]:my-0 [&_kbd]:-translate-y-1/2 [&_kbd]:border-none [&_kbd]:bg-green-200 dark:[&_kbd]:bg-neutral-700"
{...searchProps}
/>
{search}

<CallToAction
className="ml-4 max-lg:hidden"
Expand Down
10 changes: 7 additions & 3 deletions packages/components/src/components/icons/yoga.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/components/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ export * from './contact-us';
export { Giscus } from './giscus';
export * from './version-dropdown';
export * from './dropdown';
export { FrequentlyAskedQuestions } from './faq/index.js';
export { ComparisonTable } from './comparison-table/index.js';
2 changes: 1 addition & 1 deletion packages/components/src/components/legacy-package-cmd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const LegacyPackageCmd = ({
<Pre data-filename="Terminal" data-copy="" data-language="sh" data-theme="default">
<code>
{/* eslint-disable-next-line tailwindcss/no-custom-classname */}
<span className="line">{commands[index]}</span>
<span className="line mx-4">{commands[index]}</span>
</code>
</Pre>
</Tabs.Tab>
Expand Down
15 changes: 15 additions & 0 deletions packages/components/src/server/body.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client';

import { FC, ReactNode } from 'react';
import { usePathname } from 'next/navigation';

export const Body: FC<{
lightOnlyPages: string[];
children: ReactNode;
}> = ({ lightOnlyPages, children }) => {
const pathname = usePathname();

const isLightOnlyPage = lightOnlyPages.includes(pathname);

return <body className={isLightOnlyPage ? 'light text-green-1000' : undefined}>{children}</body>;
};
4 changes: 3 additions & 1 deletion packages/components/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export {
} from 'nextra/page-map';
export { evaluate } from 'nextra/evaluate';
export { fetchPackageInfo } from './npm.js';
export { sharedMetaItems } from './shared-meta-items';
export { sharedMetaItems } from './shared-meta-items.js';
export { Body } from './body.client.js';
export { remarkLinkRewrite } from './remark-link-rewrite.js';

/**
* Contain `getPageMap` import which imports `metadata` from pages, in case importing from
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/server/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,24 @@

const require = createRequire(import.meta.url);

function getFrontMatterASTObject(node: any) {

Check warning on line 19 in packages/components/src/server/next.config.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
const [n] = node.data!.estree!.body;
return (n as any).declaration.declarations[0].init.properties;

Check warning on line 21 in packages/components/src/server/next.config.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
}

function isExportNode(node: any, varName: string) {

Check warning on line 24 in packages/components/src/server/next.config.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
if (node.type !== 'mdxjsEsm') return false;
const [n] = node.data!.estree!.body;

if (n.type !== 'ExportNamedDeclaration') return false;

const name = (n as any).declaration?.declarations?.[0].id.name;

Check warning on line 30 in packages/components/src/server/next.config.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
if (!name) return false;

return name === varName;
}

const rehypeCheckFrontMatter: Plugin<[], any> = () => (ast, file) => {

Check warning on line 36 in packages/components/src/server/next.config.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
const [filePath] = file.history;
// Skip if no file path (e.g. dynamic mdx without filePath provided)
if (!filePath) return;
Expand All @@ -54,9 +54,9 @@
}
}

const frontMatterNode = ast.children.find((node: any) => isExportNode(node, 'metadata'))!;

Check warning on line 57 in packages/components/src/server/next.config.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
const frontMatter = getFrontMatterASTObject(frontMatterNode);
const description = frontMatter.find((o: any) => o.key.value === 'description')?.value

Check warning on line 59 in packages/components/src/server/next.config.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
.value as string;

if (!description) {
Expand All @@ -72,7 +72,7 @@
}
};

export const defaultNextraOptions: NextraConfig = {
export const defaultNextraOptions = {
defaultShowCopyCode: true,
whiteListTagsStyling: ['iframe', 'video', 'source'],
search: {
Expand All @@ -83,7 +83,7 @@
// Should be rehype since frontMatter is attached in remark plugins
rehypePlugins: process.env.NODE_ENV === 'production' ? [rehypeCheckFrontMatter] : [],
},
};
} satisfies NextraConfig;

// this won't be emitted if it's inline in parens
export interface WithGuildDocsOptions extends NextConfig {
Expand Down
21 changes: 21 additions & 0 deletions packages/components/src/server/remark-link-rewrite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Root } from 'mdast';
import { Plugin } from 'unified';
import { visit } from 'unist-util-visit';

const EXTERNAL_URL_RE = /^https?:\/\//;

export type RemarkLinkRewriteOptions = {
pattern: RegExp;
replace: string;
};

export const remarkLinkRewrite: Plugin<[RemarkLinkRewriteOptions], Root> =
({ pattern, replace }) =>
ast => {
visit(ast, 'link', node => {
if (EXTERNAL_URL_RE.test(node.url)) {
return;
}
node.url = node.url.replace(pattern, replace);
});
};
Loading
Loading