|
1 | | -import React, { useCallback, useId, useMemo, useState } from 'react'; |
| 1 | +import cn from 'classnames'; |
| 2 | +import React from 'react'; |
2 | 3 | import Switcher from '@rescui/switcher'; |
3 | 4 | import Checkbox from '@rescui/checkbox'; |
4 | 5 | import { createTextCn } from '@rescui/typography'; |
5 | | -import '@jetbrains/kotlin-web-site-ui/out/components/layout'; |
6 | 6 | import styles from './case-studies-filter.module.css'; |
| 7 | +import { CasePlatform, CaseType, CaseTypeSwitch, PlatformNames, Platforms } from '../case-studies'; |
| 8 | +import { useQueryState } from '../../../hooks'; |
| 9 | +import { parseCompose, parsePlatforms, parseType, serializeCompose, serializePlatforms, serializeType } from '../utils'; |
| 10 | + |
| 11 | +const caseTypeOptions: Array<{ value: CaseTypeSwitch, label: string }> = [ |
| 12 | + { value: 'all', label: 'All' }, |
| 13 | + { value: 'multiplatform', label: 'Kotlin Multiplatform' }, |
| 14 | + { value: 'server-side', label: 'Server-side' } |
| 15 | +]; |
7 | 16 |
|
8 | 17 | export const CaseStudiesFilter: React.FC = () => { |
9 | 18 | const darkTextCn = createTextCn('dark'); |
10 | | - // Case study type switcher |
11 | | - const typeOptions = useMemo( |
12 | | - () => [ |
13 | | - { value: 'all', label: 'All' }, |
14 | | - { value: 'kotlin-multiplatform', label: 'Kotlin Multiplatform' }, |
15 | | - { value: 'server-side', label: 'Server-side' }, |
16 | | - ], [] |
17 | | - ); |
18 | | - const [type, setType] = useState<string>('all'); |
19 | | - const onTypeChange = useCallback((value: string) => setType(value), []); |
20 | 19 |
|
21 | | - // Code shared across (checkboxes) |
22 | | - const codeSharedOptions = useMemo( |
23 | | - () => [ |
24 | | - { id: 'android', label: 'Android' }, |
25 | | - { id: 'ios', label: 'iOS' }, |
26 | | - { id: 'desktop', label: 'Desktop' }, |
27 | | - { id: 'frontend', label: 'Frontend' }, |
28 | | - { id: 'backend', label: 'Backend' }, |
29 | | - ], [] |
30 | | - ); |
31 | | - const [codeShared, setCodeShared] = useState<string[]>([]); |
32 | | - const toggleCodeShared = useCallback((id: string) => { |
33 | | - setCodeShared((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])); |
34 | | - }, []); |
| 20 | + // State synchronized with URL |
| 21 | + const [type, setType] = useQueryState<CaseTypeSwitch>('type', parseType, serializeType); |
| 22 | + const [platforms, setPlatforms] = useQueryState<CasePlatform[]>('platforms', parsePlatforms, serializePlatforms); |
| 23 | + const [compose, setCompose] = useQueryState<boolean>('compose', parseCompose, serializeCompose); |
35 | 24 |
|
36 | | - // UI Technology (checkboxes) |
37 | | - const uiTechOptions = useMemo( |
38 | | - () => [ |
39 | | - { id: 'built-with-compose-multiplatform', label: 'Built with Compose Multiplatform' }, |
40 | | - ], [] |
41 | | - ); |
42 | | - const [uiTech, setUiTech] = useState<string[]>([]); |
43 | | - const toggleUiTech = useCallback((id: string) => { |
44 | | - setUiTech((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])); |
45 | | - }, []); |
| 25 | + const togglePlatform = (id: CasePlatform) => { |
| 26 | + let nextPlatforms = platforms.includes(id) |
| 27 | + ? platforms.filter((x) => x !== id) |
| 28 | + : [...platforms, id]; |
| 29 | + if (nextPlatforms.length === 0) { |
| 30 | + // If user unchecks all, reset to all selected |
| 31 | + nextPlatforms = [...Platforms]; |
| 32 | + } |
| 33 | + setPlatforms(nextPlatforms); |
| 34 | + }; |
| 35 | + |
| 36 | + const toggleCompose = () => { |
| 37 | + const nextCompose = !compose; |
| 38 | + setCompose(nextCompose, () => setType('multiplatform')); |
| 39 | + }; |
46 | 40 |
|
47 | | - // for accessibility ids |
48 | | - const typeTitleId = useId(); |
49 | | - const codeSharedTitleId = useId(); |
50 | | - const uiTechTitleId = useId(); |
| 41 | + const showKmpFilters = type === 'multiplatform' || type === 'all'; |
51 | 42 |
|
52 | 43 | return ( |
53 | | - <section data-testid="case-studies-filter" aria-label="Case Studies Filter" className={styles.wrapper}> |
54 | | - <div className={'ktl-layout ktl-layout--center'}> |
55 | | - <h2 className={styles.title}> |
56 | | - <span className={darkTextCn('rs-h4')}>Filters</span> |
57 | | - </h2> |
58 | | - <div className={styles.inner}> |
59 | | - {/* Case study type */} |
60 | | - <div className={`${styles.group} ${styles.groupType}`} role="group" aria-labelledby={typeTitleId} data-test="filter-type"> |
61 | | - <h3 id={typeTitleId} className={styles.groupTitle}><span className={darkTextCn('rs-h4')}>Case study type</span></h3> |
62 | | - <div className={styles.switcherSmall}> |
63 | | - <Switcher mode={'rock'} value={type} onChange={onTypeChange} options={typeOptions} /> |
64 | | - </div> |
65 | | - </div> |
| 44 | + <section className="ktl-layout ktl-layout--center" data-testid="case-studies-filter" |
| 45 | + aria-label="Case Studies Filter"> |
| 46 | + <div className={styles.content}> |
| 47 | + <div className={styles.group} role="group" aria-labelledby="case-study-type-title" |
| 48 | + data-test="filter-type"> |
| 49 | + <h3 id="case-study-type-title" className={cn(styles.groupTitle, darkTextCn('rs-h4'))}>Case study |
| 50 | + type</h3> |
| 51 | + <Switcher mode={'rock'} value={type} onChange={setType} options={caseTypeOptions} /> |
| 52 | + </div> |
66 | 53 |
|
67 | | - {/* Code shared across */} |
68 | | - <div className={styles.group} role="group" aria-labelledby={codeSharedTitleId} data-test="filter-code-shared"> |
69 | | - <h3 id={codeSharedTitleId} className={styles.groupTitle}><span className={darkTextCn('rs-h4')}>Code shared across</span></h3> |
| 54 | + {showKmpFilters && ( |
| 55 | + <div className={styles.group} role="group" aria-labelledby="code-shared-across-titile" |
| 56 | + data-test="filter-code-shared"> |
| 57 | + <h3 id="code-shared-across-titile" |
| 58 | + className={cn(styles.groupTitle, darkTextCn('rs-h4'))}>Code shared across</h3> |
70 | 59 | <div className={styles.checkboxes}> |
71 | | - {codeSharedOptions.map((opt) => { |
72 | | - const id = `code-shared-${opt.id}`; |
73 | | - const checked = codeShared.includes(opt.id); |
74 | | - return ( |
75 | | - <Checkbox |
76 | | - key={opt.id} |
77 | | - checked={checked} |
78 | | - onChange={() => toggleCodeShared(opt.id)} |
79 | | - mode="classic" |
80 | | - size="m" |
81 | | - > |
82 | | - {opt.label} |
83 | | - </Checkbox> |
84 | | - ); |
85 | | - })} |
| 60 | + {Platforms.map((platformId) => |
| 61 | + <Checkbox |
| 62 | + key={platformId} |
| 63 | + checked={platforms.includes(platformId)} |
| 64 | + onChange={() => togglePlatform(platformId)} |
| 65 | + mode="classic" |
| 66 | + size="m" |
| 67 | + > |
| 68 | + {PlatformNames[platformId]} |
| 69 | + </Checkbox> |
| 70 | + )} |
86 | 71 | </div> |
87 | 72 | </div> |
| 73 | + )} |
88 | 74 |
|
89 | | - {/* UI technology */} |
90 | | - <div className={styles.group} role="group" aria-labelledby={uiTechTitleId} data-test="filter-ui-technology"> |
91 | | - <h3 id={uiTechTitleId} className={styles.groupTitle}><span className={darkTextCn('rs-h4')}>UI technology</span></h3> |
| 75 | + {showKmpFilters && ( |
| 76 | + <div className={styles.group} role="group" aria-labelledby="ui-technology-title" |
| 77 | + data-test="filter-ui-technology"> |
| 78 | + <h3 id="ui-technology-title" |
| 79 | + className={cn(styles.groupTitle, darkTextCn('rs-h4'))}>UI technology</h3> |
92 | 80 | <div className={styles.checkboxes}> |
93 | | - {uiTechOptions.map((opt) => { |
94 | | - const id = `ui-tech-${opt.id}`; |
95 | | - const checked = uiTech.includes(opt.id); |
96 | | - return ( |
97 | | - <Checkbox |
98 | | - key={opt.id} |
99 | | - checked={checked} |
100 | | - onChange={() => toggleUiTech(opt.id)} |
101 | | - mode="classic" |
102 | | - size="m" |
103 | | - > |
104 | | - {opt.label} |
105 | | - </Checkbox> |
106 | | - ); |
107 | | - })} |
| 81 | + <Checkbox |
| 82 | + className={styles.checkbox} |
| 83 | + checked={compose} |
| 84 | + onChange={toggleCompose} |
| 85 | + mode="classic" |
| 86 | + size="m" |
| 87 | + > |
| 88 | + Built with Compose Multiplatform <img |
| 89 | + src="/images/case-studies/compose-multiplatform.svg" alt="" /> |
| 90 | + </Checkbox> |
108 | 91 | </div> |
109 | 92 | </div> |
110 | | - </div> |
| 93 | + )} |
111 | 94 | </div> |
112 | 95 | </section> |
113 | 96 | ); |
|
0 commit comments