Skip to content

Commit 80a5ca2

Browse files
m-hulbertGregHolmes
authored andcommitted
Add a guides section into the navigation
1 parent e8d88da commit 80a5ca2

File tree

15 files changed

+984
-2
lines changed

15 files changed

+984
-2
lines changed

data/onCreatePage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const pageLayoutOptions: Record<string, LayoutOptions> = {
2121
},
2222
'/docs/sdks': { leftSidebar: false, rightSidebar: false, template: 'sdk', mdx: false },
2323
'/examples': { leftSidebar: false, rightSidebar: false, template: 'examples', mdx: false },
24+
'/guides': { leftSidebar: false, rightSidebar: false, template: 'guides', mdx: false },
2425
'/docs/404': { leftSidebar: false, rightSidebar: false, template: '404', mdx: false },
2526
};
2627

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import Icon from '@ably/ui/core/Icon';
3+
import cn from '@ably/ui/core/utils/cn';
4+
5+
const GuidesCheckbox = ({
6+
label,
7+
name,
8+
value,
9+
disabled = false,
10+
isChecked = false,
11+
handleSelect,
12+
indented = false,
13+
}: {
14+
label: string;
15+
name: string;
16+
value: string;
17+
disabled?: boolean;
18+
isChecked?: boolean;
19+
handleSelect: (e: React.ChangeEvent<HTMLInputElement>) => void;
20+
indented?: boolean;
21+
}) => {
22+
return (
23+
<div className={cn('flex items-center mb-0', indented && 'ml-4')}>
24+
<input
25+
data-ui-checkbox-native=""
26+
type="checkbox"
27+
id={name}
28+
data-testid={name}
29+
name={name}
30+
className="ui-checkbox-input"
31+
value={value}
32+
checked={isChecked}
33+
disabled={disabled}
34+
onChange={(e) => handleSelect(e)}
35+
/>
36+
<div
37+
data-ui-checkbox-styled=""
38+
className={cn(['ui-checkbox-styled', disabled && '!border-neutral-800 !bg-orange-600'])}
39+
>
40+
<Icon
41+
size="1rem"
42+
name="icon-gui-check-outline"
43+
additionalCSS={cn(['ui-checkbox-styled-tick cursor-pointer', disabled && 'text-neutral-000'])}
44+
/>
45+
</div>
46+
<label htmlFor={name} className="ui-text-p4 text-neutral-900">
47+
{label}
48+
</label>
49+
</div>
50+
);
51+
};
52+
53+
export default GuidesCheckbox;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { ChangeEvent, useCallback, useEffect, useState } from 'react';
2+
import GuidesGrid from './GuidesGrid';
3+
import GuidesFilter from './GuidesFilter';
4+
import { ImageProps } from '../Image';
5+
import patternGrid from './images/pattern-grid.svg';
6+
import mobileGrid from './images/mobile-grid.svg';
7+
import { guides } from '../../data/guides';
8+
import { filterSearchGuides, SelectedFilters } from './filter-search-guides';
9+
import GuidesNoResults from './GuidesNoResults';
10+
import { useLocation } from '@reach/router';
11+
import { GuideProduct, AIProvider } from '../../data/guides/types';
12+
import { products } from '../../data/guides';
13+
14+
const GuidesContent = ({ guideImages }: { guideImages: ImageProps[] }) => {
15+
const location = useLocation();
16+
17+
// Parse product and provider query parameters
18+
const getInitialFilters = (): SelectedFilters => {
19+
const params = new URLSearchParams(location.search);
20+
const productParam = params.get('product');
21+
const providerParam = params.get('provider');
22+
const validProductNames = Object.keys(products);
23+
24+
const initialProducts: GuideProduct[] = productParam
25+
? productParam
26+
.split(',')
27+
.map((p) => p.trim())
28+
.filter((product): product is GuideProduct => validProductNames.includes(product))
29+
: [];
30+
31+
const validProviderNames: AIProvider[] = ['anthropic', 'openai'];
32+
const initialProviders: AIProvider[] = providerParam
33+
? providerParam
34+
.split(',')
35+
.map((p) => p.trim())
36+
.filter((provider): provider is AIProvider => validProviderNames.includes(provider as AIProvider))
37+
: [];
38+
39+
return {
40+
products: initialProducts,
41+
aiProviders: initialProviders,
42+
};
43+
};
44+
45+
const [selected, setSelected] = useState<SelectedFilters>(getInitialFilters);
46+
const [searchTerm, setSearchTerm] = useState('');
47+
const [filteredGuides, setFilteredGuides] = useState(guides);
48+
49+
const handleSearch = useCallback((e: ChangeEvent<HTMLInputElement>) => {
50+
setSearchTerm(e.target.value);
51+
}, []);
52+
53+
useEffect(() => {
54+
const filtered = filterSearchGuides(guides, selected, searchTerm);
55+
setFilteredGuides(filtered);
56+
}, [selected, searchTerm]);
57+
58+
return (
59+
<>
60+
<section className="mx-auto px-6 md:px-0 relative">
61+
<div className="w-full sm:w-1/2 max-w-[37.5rem] pt-20 sm:pt-24">
62+
<h1 className="ui-text-title text-title">Guides</h1>
63+
<p className="ui-text-sub-header mt-4">
64+
In-depth guides to help you build realtime features with Ably and understand how best to architect an app
65+
for your use case.
66+
</p>
67+
</div>
68+
<div className="w-full my-10 sm:my-16 flex flex-col sm:flex-row gap-x-10">
69+
<div className="w-full sm:w-[20%] relative">
70+
<GuidesFilter selected={selected} setSelected={setSelected} handleSearch={handleSearch} />
71+
</div>
72+
<div className="w-full sm:w-[80%] mt-10 sm:mt-0">
73+
{filteredGuides.length > 0 ? (
74+
<GuidesGrid guideImages={guideImages} guides={filteredGuides} searchTerm={searchTerm} />
75+
) : (
76+
<GuidesNoResults />
77+
)}
78+
</div>
79+
</div>
80+
</section>
81+
82+
<img
83+
src={patternGrid}
84+
alt=""
85+
aria-hidden="true"
86+
className="absolute -z-10 right-0 top-16 hidden sm:block w-[60%] md:w-[40%] pointer-events-none"
87+
/>
88+
89+
<img
90+
src={mobileGrid}
91+
alt=""
92+
aria-hidden="true"
93+
className="absolute -z-10 right-0 top-16 block sm:hidden pointer-events-none"
94+
/>
95+
</>
96+
);
97+
};
98+
99+
export default GuidesContent;

0 commit comments

Comments
 (0)