Skip to content

Commit da72f38

Browse files
dimaMachinarenovate[bot]github-actions[bot]
authored
<ComparisonTable>, <FAQ>, <Search> tweaks (#1927)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent c2cb941 commit da72f38

21 files changed

+414
-41
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@theguild/components": patch
3+
---
4+
dependencies updates:
5+
- Added dependency [`@radix-ui/react-accordion@^1.2.2` ↗︎](https://www.npmjs.com/package/@radix-ui/react-accordion/v/1.2.2) (to `dependencies`)
6+
- Added dependency [`@radix-ui/react-icons@^1.3.2` ↗︎](https://www.npmjs.com/package/@radix-ui/react-icons/v/1.3.2) (to `dependencies`)
7+
- Added dependency [`[email protected]` ↗︎](https://www.npmjs.com/package/unist-util-visit/v/5.0.0) (to `dependencies`)

.changeset/three-jeans-impress.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@theguild/components': minor
3+
---
4+
5+
- add `<ComparisonTable>` component
6+
- add `<AttachPageFAQSchema>` component
7+
- add `<FrequentlyAskedQuestions>` component
8+
- move `<Search>` styles from Tailwind CSS classes to `style.css` because in Yoga we use `<VersionedSearch>`
9+
- add `remarkLinkRewrite` plugin

packages/components/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"dependencies": {
5353
"@giscus/react": "3.1.0",
5454
"@next/bundle-analyzer": "15.1.4",
55+
"@radix-ui/react-accordion": "^1.2.2",
56+
"@radix-ui/react-icons": "^1.3.2",
5557
"@radix-ui/react-navigation-menu": "^1.2.0",
5658
"clsx": "2.1.1",
5759
"fuzzy": "0.1.3",
@@ -60,13 +62,15 @@
6062
"react-paginate": "8.2.0",
6163
"react-player": "2.16.0",
6264
"semver": "^7.3.8",
63-
"tailwind-merge": "^2.5.2"
65+
"tailwind-merge": "^2.5.2",
66+
"unist-util-visit": "5.0.0"
6467
},
6568
"devDependencies": {
66-
"@svgr/babel-plugin-remove-jsx-attribute": "^8.0.0",
69+
"@svgr/plugin-svgo": "^8.1.0",
6770
"@theguild/editor": "workspace:*",
6871
"@theguild/tailwind-config": "0.6.2",
6972
"@types/dedent": "0.7.2",
73+
"@types/mdast": "4.0.4",
7074
"@types/react": "18.3.18",
7175
"@types/react-dom": "18.3.5",
7276
"@types/semver": "7.5.8",
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { ComponentProps, FC } from 'react';
2+
import { cn } from '@theguild/components';
3+
4+
const Table: FC<ComponentProps<'table'>> = ({ className, ...props }) => {
5+
return (
6+
<table
7+
className={cn(
8+
'x:block x:overflow-x-auto nextra-scrollbar overflow-x-auto rounded-2xl border border-green-200',
9+
className,
10+
)}
11+
{...props}
12+
/>
13+
);
14+
};
15+
16+
const TableRow: FC<ComponentProps<'tr'> & { highlight?: boolean }> = ({
17+
highlight,
18+
className,
19+
...props
20+
}) => {
21+
return <tr className={cn(highlight && 'bg-green-100', className)} {...props} />;
22+
};
23+
24+
const cellStyle = cn(
25+
'border border-green-200 p-4 first:border-l-0 last:border-r-0',
26+
'[tbody_&]:border-b-0 [thead_&]:border-t-0',
27+
'first:sticky',
28+
'first:left-0',
29+
'max-sm:first:drop-shadow-2xl',
30+
'first:bg-[rgb(var(--nextra-bg))]',
31+
);
32+
33+
const TableHeader: FC<ComponentProps<'th'>> = ({ className, ...props }) => {
34+
return <th className={cn(cellStyle, 'font-medium', className)} {...props} />;
35+
};
36+
37+
const TableCell: FC<ComponentProps<'td'>> = ({ className, ...props }) => {
38+
return <td className={cn(cellStyle, className)} {...props} />;
39+
};
40+
41+
export const ComparisonTable = Object.assign(Table, {
42+
Row: TableRow,
43+
Header: TableHeader,
44+
Cell: TableCell,
45+
});

packages/components/src/components/explore-main-product-cards.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function ExploreMainProductCards({
2929
</Heading>
3030
<TextLink
3131
href={isHive ? '/ecosystem' : 'https://the-guild.dev/graphql/hive/ecosystem'}
32-
className="mt-4 lg:mt-6"
32+
className="mt-4 text-green-800 lg:mt-6"
3333
>
3434
Learn more
3535
<ArrowIcon />
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client';
2+
3+
import { FC, useEffect } from 'react';
4+
import { usePathname } from 'next/navigation';
5+
6+
export const AttachPageFAQSchema: FC<{ faqPages: string[] }> = ({ faqPages }) => {
7+
const pathname = usePathname();
8+
9+
useEffect(() => {
10+
const html = document.querySelector('html')!;
11+
if (faqPages.includes(pathname) && !html.hasAttribute('itemscope')) {
12+
html.setAttribute('itemscope', '');
13+
html.setAttribute('itemtype', 'https://schema.org/FAQPage');
14+
15+
return () => {
16+
html.removeAttribute('itemscope');
17+
html.removeAttribute('itemtype');
18+
};
19+
}
20+
}, []);
21+
22+
return null;
23+
};
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
Children,
3+
cloneElement,
4+
ComponentProps,
5+
ComponentPropsWithoutRef,
6+
FC,
7+
ReactElement,
8+
ReactNode,
9+
} from 'react';
10+
import * as RadixAccordion from '@radix-ui/react-accordion';
11+
import { ChevronDownIcon } from '@radix-ui/react-icons';
12+
import { cn } from '../../cn';
13+
import { Anchor } from '../anchor';
14+
import { Heading } from '../heading';
15+
import { AttachPageFAQSchema } from './attach-page-faq-schema';
16+
17+
const UnwrapChild: FC<{ children?: ReactNode }> = props => props.children;
18+
19+
const a: FC<ComponentPropsWithoutRef<'a'>> = props => (
20+
<Anchor
21+
className="hive-focus rounded underline hover:text-blue-700"
22+
{...props}
23+
href={props.href!}
24+
>
25+
{props.children}
26+
</Anchor>
27+
);
28+
29+
const h2: FC<ComponentPropsWithoutRef<'h2'>> = props => (
30+
<Heading as="h2" size="md" className="basis-1/2" {...props} />
31+
);
32+
33+
export const FrequentlyAskedQuestions: FC<
34+
ComponentProps<typeof AttachPageFAQSchema> & {
35+
className?: string;
36+
children: ReactElement;
37+
}
38+
> = ({ className, faqPages, children }) => {
39+
return (
40+
<section
41+
className={cn(
42+
className,
43+
'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',
44+
)}
45+
>
46+
<AttachPageFAQSchema faqPages={faqPages} />
47+
{cloneElement(children, {
48+
components: {
49+
a,
50+
h2,
51+
p: UnwrapChild,
52+
ul: Accordion,
53+
li: AccordionItem,
54+
},
55+
})}
56+
</section>
57+
);
58+
};
59+
60+
const Accordion: FC<ComponentPropsWithoutRef<'ul'>> = props => (
61+
<RadixAccordion.Root asChild type="single" collapsible>
62+
<ul className="basis-1/2 divide-y divide-beige-400 max-xl:grow" {...props} />
63+
</RadixAccordion.Root>
64+
);
65+
66+
const AccordionItem: FC<ComponentPropsWithoutRef<'li'>> = props => {
67+
const texts = Children.toArray(props.children).filter(child => child !== '\n');
68+
69+
if (texts.length === 0) {
70+
return null;
71+
}
72+
73+
if (texts.length < 2) {
74+
// eslint-disable-next-line no-console
75+
console.error(texts);
76+
throw new Error(`Expected a question and an answer, got ${texts.length} items`);
77+
}
78+
79+
const [first, ...answers] = texts;
80+
81+
const question =
82+
typeof first === 'string'
83+
? first
84+
: typeof first === 'object' && 'type' in first
85+
? first.props.children
86+
: null;
87+
88+
if (!question) return null;
89+
90+
return (
91+
<RadixAccordion.Item
92+
asChild
93+
value={question}
94+
className="relative pb-0 focus-within:z-10 data-[state=open]:pb-4"
95+
itemScope
96+
itemProp="mainEntity"
97+
itemType="https://schema.org/Question"
98+
>
99+
<li>
100+
<RadixAccordion.Header>
101+
<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">
102+
<span itemProp="name">{question}</span>
103+
<ChevronDownIcon className="size-5 [[data-state='open']_&]:[transform:rotateX(180deg)]" />
104+
</RadixAccordion.Trigger>
105+
</RadixAccordion.Header>
106+
<RadixAccordion.Content
107+
forceMount
108+
className="overflow-hidden bg-white text-green-800 data-[state=closed]:hidden"
109+
itemScope
110+
itemProp="acceptedAnswer"
111+
itemType="https://schema.org/Answer"
112+
>
113+
<div itemProp="text" className="space-y-2">
114+
{answers.map((answer, i) => (
115+
<p key={i}>{answer}</p>
116+
))}
117+
</div>
118+
</RadixAccordion.Content>
119+
</li>
120+
</RadixAccordion.Item>
121+
);
122+
};

packages/components/src/components/giscus.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

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

7-
export const Giscus: FC<GiscusProps> = props => {
7+
export const Giscus: FC<
8+
Omit<GiscusProps, 'mapping'> & {
9+
mapping?: GiscusProps['mapping'];
10+
}
11+
> = props => {
812
const { resolvedTheme } = useTheme();
9-
return (
10-
<>
11-
<br />
12-
<_Giscus theme={resolvedTheme} {...props} />
13-
</>
14-
);
13+
return <Giscus_ theme={resolvedTheme} mapping="pathname" {...props} />;
1514
};

packages/components/src/components/hive-navigation/index.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, {
55
FC,
66
forwardRef,
77
Fragment,
8+
ReactElement,
89
ReactNode,
910
useEffect,
1011
useRef,
@@ -55,6 +56,7 @@ export type HiveNavigationProps = {
5556
logo?: ReactNode;
5657
navLinks?: { href: string; children: ReactNode }[];
5758
developerMenu: DeveloperMenuProps['developerMenu'];
59+
search?: ReactElement;
5860
searchProps?: ComponentProps<typeof Search>;
5961
};
6062

@@ -83,7 +85,7 @@ export function HiveNavigation({
8385
},
8486
],
8587
developerMenu,
86-
searchProps,
88+
search = <Search />,
8789
}: HiveNavigationProps) {
8890
const containerRef = useRef<HTMLDivElement>(null!);
8991

@@ -152,10 +154,7 @@ export function HiveNavigation({
152154

153155
{children}
154156

155-
<Search
156-
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"
157-
{...searchProps}
158-
/>
157+
{search}
159158

160159
<CallToAction
161160
className="ml-4 max-lg:hidden"
Lines changed: 7 additions & 3 deletions
Loading

0 commit comments

Comments
 (0)