Skip to content

Commit 717d30c

Browse files
authored
Adapt Checkbox & SearchBar to ShadCN, Add Tests, and Fix Tools Filter position (#1686)
* Implementing Checkbox.tsx * simplifying and containing Checkbox.tsx * implementing SearchBar.tsx * adjusting border color * adding cypress test file for checkbox * added a test file for search bar * fixing issue #1279 * fixing spacing * spacing adjustments * mobile adjustment * last spacing adjustments * format adjustments * adding prop types for format fixing * adjusting the cypress test for searchbar * small format fix * keeping original width
1 parent 6e4ced5 commit 717d30c

File tree

6 files changed

+254
-45
lines changed

6 files changed

+254
-45
lines changed

components/ui/input.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* eslint-disable linebreak-style */
2+
import * as React from 'react';
3+
import PropTypes from 'prop-types';
4+
5+
import { cn } from '@/lib/utils';
6+
7+
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
8+
return (
9+
<input
10+
type={type}
11+
data-slot='input'
12+
className={cn(
13+
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
14+
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
15+
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
16+
className,
17+
)}
18+
{...props}
19+
/>
20+
);
21+
}
22+
23+
Input.propTypes = {
24+
className: PropTypes.string,
25+
type: PropTypes.string,
26+
};
27+
28+
export { Input };

cypress/components/Checkbox.cy.tsx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/* eslint-disable cypress/unsafe-to-chain-command */
2+
import React from 'react';
3+
import Checkbox from '../../pages/tools/components/ui/Checkbox';
4+
5+
describe('Checkbox Component', () => {
6+
it('renders with required props', () => {
7+
cy.mount(
8+
<Checkbox label='Test Checkbox' value='test' name='test-checkbox' />,
9+
);
10+
cy.get('label').should('exist');
11+
cy.get('button[role="checkbox"]').should('exist');
12+
cy.contains('Test Checkbox').should('be.visible');
13+
});
14+
15+
it('handles disabled state', () => {
16+
cy.mount(
17+
<Checkbox
18+
label='Disabled Checkbox'
19+
value='test'
20+
name='test-checkbox'
21+
disabled={true}
22+
/>,
23+
);
24+
cy.get('button[role="checkbox"]').should('have.attr', 'disabled');
25+
});
26+
27+
describe('Light Mode', () => {
28+
beforeEach(() => {
29+
cy.mount(
30+
<Checkbox label='Test Checkbox' value='test' name='test-checkbox' />,
31+
);
32+
});
33+
34+
it('renders correctly and handles interactions', () => {
35+
// Check initial render
36+
cy.contains('Test Checkbox').should('be.visible');
37+
cy.get('[data-state="unchecked"]').should('exist');
38+
39+
// Check styling
40+
cy.get('label').should('have.class', 'flex');
41+
cy.get('button[role="checkbox"]')
42+
.should('have.class', 'h-5')
43+
.and('have.class', 'w-5')
44+
.and('have.class', 'border-gray-500');
45+
46+
// Check interaction
47+
cy.get('button[role="checkbox"]')
48+
.click()
49+
.should('have.attr', 'data-state', 'checked')
50+
.click()
51+
.should('have.attr', 'data-state', 'unchecked');
52+
});
53+
54+
it('handles checked state prop', () => {
55+
cy.mount(
56+
<Checkbox
57+
label='Test Checkbox'
58+
value='test'
59+
name='test-checkbox'
60+
checked={true}
61+
/>,
62+
);
63+
cy.get('button[role="checkbox"]')
64+
.should('have.attr', 'data-state', 'checked')
65+
.and('have.class', 'data-[state=checked]:bg-blue-500')
66+
.and('have.class', 'data-[state=checked]:border-blue-500')
67+
.and('have.class', 'data-[state=checked]:text-white');
68+
});
69+
});
70+
71+
describe('Dark Mode', () => {
72+
beforeEach(() => {
73+
cy.mount(
74+
<div className='dark'>
75+
<Checkbox label='Test Checkbox' value='test' name='test-checkbox' />
76+
</div>,
77+
);
78+
});
79+
80+
it('renders with correct dark mode styling', () => {
81+
// Check label styling
82+
cy.get('label')
83+
.should('have.class', 'dark:bg-slate-900')
84+
.and('have.class', 'dark:border-slate-700');
85+
86+
// Check text color
87+
cy.get('span').should('have.class', 'dark:text-slate-300');
88+
89+
// Check checkbox styling
90+
cy.get('button[role="checkbox"]').should(
91+
'have.class',
92+
'dark:border-slate-600',
93+
);
94+
});
95+
96+
it('handles checked state in dark mode', () => {
97+
cy.mount(
98+
<div className='dark'>
99+
<Checkbox
100+
label='Test Checkbox'
101+
value='test'
102+
name='test-checkbox'
103+
checked={true}
104+
/>
105+
</div>,
106+
);
107+
cy.get('button[role="checkbox"]')
108+
.should('have.attr', 'data-state', 'checked')
109+
.and('have.class', 'dark:data-[state=checked]:bg-[#bfdbfe]')
110+
.and('have.class', 'dark:data-[state=checked]:text-black');
111+
});
112+
});
113+
});

cypress/components/SearchBar.cy.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { mount } from 'cypress/react18';
3+
import SearchBar from '@/pages/tools/components/SearchBar';
4+
import type { Transform } from '@/pages/tools/hooks/useToolsTransform';
5+
6+
describe('SearchBar Component', () => {
7+
const mockTransform: Transform = {
8+
query: '',
9+
sortBy: 'name',
10+
sortOrder: 'ascending',
11+
groupBy: 'toolingTypes',
12+
licenses: [],
13+
languages: [],
14+
drafts: [],
15+
toolingTypes: [],
16+
environments: [],
17+
showObsolete: 'false',
18+
supportsBowtie: 'false',
19+
};
20+
21+
beforeEach(() => {
22+
mount(<SearchBar transform={mockTransform} />);
23+
});
24+
25+
it('renders the search input', () => {
26+
cy.get('input[type="text"]').should('exist');
27+
cy.get('input[type="text"]').should('have.attr', 'placeholder', 'Search');
28+
});
29+
30+
it('updates input value when typing', () => {
31+
const testQuery = 'test search';
32+
cy.get('input[type="text"]').type(testQuery);
33+
cy.get('input[type="text"]').should('have.value', testQuery);
34+
});
35+
36+
it('updates when transform.query changes', () => {
37+
const newQuery = 'new search query';
38+
mount(<SearchBar transform={{ ...mockTransform, query: newQuery }} />);
39+
cy.get('input[type="text"]').should('have.value', newQuery);
40+
});
41+
});

pages/tools/components/SearchBar.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useEffect, useState } from 'react';
2+
import { Input } from '@/components/ui/input';
23
import type { Transform } from '../hooks/useToolsTransform';
34

45
const SearchBar = ({ transform }: { transform: Transform }) => {
@@ -14,16 +15,14 @@ const SearchBar = ({ transform }: { transform: Transform }) => {
1415

1516
return (
1617
<div className='w-full max-w-md mx-auto my-6 lg:my-auto'>
17-
<div className='relative'>
18-
<input
19-
type='text'
20-
className='w-full px-4 py-2 border dark:border-slate-900 rounded-md shadow-sm focus:outline-none focus:ring focus:border-blue-300 dark:bg-slate-900'
21-
placeholder='Search'
22-
name='query'
23-
value={query}
24-
onChange={changeHandler}
25-
/>
26-
</div>
18+
<Input
19+
type='text'
20+
className='dark:border-slate-900 focus:border-blue-300 dark:bg-slate-900'
21+
placeholder='Search'
22+
name='query'
23+
value={query}
24+
onChange={changeHandler}
25+
/>
2726
</div>
2827
);
2928
};

pages/tools/components/ui/Checkbox.tsx

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,27 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React from 'react';
2+
import { Checkbox as ShadcnCheckbox } from '@/components/ui/checkbox';
23

34
export default function Checkbox({
45
label,
56
value,
67
name,
78
checked,
9+
disabled,
810
}: {
911
label: string;
1012
value: string;
1113
name: string;
1214
checked?: boolean;
15+
disabled?: boolean;
1316
}) {
14-
const [isChecked, setIsChecked] = useState(checked);
15-
16-
useEffect(() => {
17-
setIsChecked(checked);
18-
}, [checked]);
19-
20-
const handleChange = () => {
21-
setIsChecked((prevChecked) => !prevChecked);
22-
};
23-
2417
return (
25-
<label className='flex items-center gap-3 px-4 py-2 cursor-pointer'>
26-
<input
27-
type='checkbox'
18+
<label className='flex items-center gap-3 px-4 py-2 cursor-pointer bg-slate-200 dark:bg-slate-900 hover:bg-slate-300 dark:hover:bg-slate-800 transition-colors duration-200 border border-slate-300 dark:border-slate-700 rounded-md my-2'>
19+
<ShadcnCheckbox
2820
value={value}
2921
name={name}
30-
checked={isChecked}
31-
onChange={handleChange}
22+
defaultChecked={checked}
23+
disabled={disabled}
24+
className='h-5 w-5 border data-[state=checked]:bg-blue-500 data-[state=checked]:border-blue-500 data-[state=checked]:text-white dark:data-[state=checked]:bg-[#bfdbfe] dark:data-[state=checked]:border-[#bfdbfe] dark:data-[state=checked]:text-black border-gray-500 dark:border-slate-600'
3225
/>
3326
<span className='text-gray-700 dark:text-slate-300 font-medium'>
3427
{label}

pages/tools/index.page.tsx

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React, { useState } from 'react';
1+
/* eslint-disable linebreak-style */
2+
import React, { useState, useEffect } from 'react';
23
import fs from 'fs';
34
import Link from 'next/link';
45
import Head from 'next/head';
@@ -101,6 +102,16 @@ export default function ToolingPage({
101102
filterCriteria,
102103
}: ToolingPageProps) {
103104
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
105+
const [isMobile, setIsMobile] = useState(false);
106+
107+
useEffect(() => {
108+
const handleResize = () => {
109+
setIsMobile(window.innerWidth < 1024);
110+
};
111+
handleResize();
112+
window.addEventListener('resize', handleResize);
113+
return () => window.removeEventListener('resize', handleResize);
114+
}, []);
104115

105116
const {
106117
numberOfTools,
@@ -142,28 +153,52 @@ export default function ToolingPage({
142153

143154
<div className='grid grid-cols-1 lg:grid-cols-4 mx-4 md:mx-12 min-h-screen'>
144155
<div
145-
className={`absolute lg:static top-10 lg:top-auto left-0 lg:left-auto mt-24 w-screen lg:w-auto h-full lg:h-auto bg-white dark:bg-slate-800 lg:bg-transparent transition-transform lg:transform-none duration-300 lg:duration-0 ease-in-out overflow-y-auto ${isSidebarOpen ? '-translate-x-0' : '-translate-x-full'} z-5`}
146-
style={{ height: 'calc(100% - 4rem)' }}
156+
className={`
157+
lg:fixed absolute top-0 lg:top-0 left-0 lg:left-auto
158+
mt-0 lg:mt-20
159+
w-screen lg:w-auto
160+
bg-white dark:bg-slate-800 lg:bg-transparent
161+
transition-transform lg:transform-none duration-300 lg:duration-0 ease-in-out
162+
z-5
163+
${isSidebarOpen ? '-translate-x-0' : '-translate-x-full'}
164+
${isMobile && isSidebarOpen ? 'overflow-hidden' : 'overflow-y-auto lg:overflow-y-hidden'}
165+
`}
166+
style={{
167+
height: isMobile
168+
? isSidebarOpen
169+
? 'calc(100vh - 4.5rem)'
170+
: '0'
171+
: 'calc(100vh - 4.5rem)',
172+
maxHeight: 'calc(100vh - 4.5rem)',
173+
bottom: 0,
174+
scrollbarWidth: 'none',
175+
position: 'sticky',
176+
top: '4.5rem',
177+
}}
147178
>
148-
<div className='hidden lg:block'>
149-
<h1 className='text-h1mobile md:text-h1 font-bold lg:ml-4 lg:mt-6'>
150-
{numberOfTools}
151-
</h1>
152-
<div className='text-xl text-slate-900 dark:text-slate-300 font-bold lg:ml-6'>
153-
Tools
179+
<div className='h-full flex flex-col'>
180+
<div className='flex-1 overflow-y-auto scrollbar-hidden min-h-0 px-2 lg:px-0 pb-2'>
181+
<div className='hidden lg:block pt-8'>
182+
<h1 className='text-h1mobile md:text-h1 font-bold lg:ml-4'>
183+
{numberOfTools}
184+
</h1>
185+
<div className='text-xl text-slate-900 dark:text-slate-300 font-bold lg:ml-6 mb-4'>
186+
Tools
187+
</div>
188+
</div>
189+
<Sidebar
190+
filterCriteria={filterCriteria}
191+
transform={transform}
192+
setTransform={setTransform}
193+
resetTransform={resetTransform}
194+
setIsSidebarOpen={setIsSidebarOpen}
195+
/>
154196
</div>
155197
</div>
156-
<Sidebar
157-
filterCriteria={filterCriteria}
158-
transform={transform}
159-
setTransform={setTransform}
160-
resetTransform={resetTransform}
161-
setIsSidebarOpen={setIsSidebarOpen}
162-
/>
163198
</div>
164199

165200
<main
166-
className={`md:col-span-3 lg:mt-20 lg:w-full mx-4 md:mx-0 ${isSidebarOpen ? 'hidden lg:block' : ''}`}
201+
className={`md:col-span-3 lg:mt-20 lg:w-full mx-4 md:mx-0 lg:!ml-[20px] ${isSidebarOpen ? 'hidden lg:block' : ''}`}
167202
>
168203
<Headline1>JSON Schema Tooling</Headline1>
169204
<p className='text-slate-600 block leading-7 pb-1 dark:text-slate-300'>
@@ -184,7 +219,7 @@ export default function ToolingPage({
184219
>
185220
<Image
186221
src='/img/tools/adding_your_tool.png'
187-
className='rounded-sm '
222+
className='rounded-sm'
188223
height={68}
189224
width={190}
190225
alt='adding your tool'
@@ -205,7 +240,7 @@ export default function ToolingPage({
205240
>
206241
<Image
207242
src='/img/tools/try_bowtie.png'
208-
className='rounded-sm '
243+
className='rounded-sm'
209244
height={68}
210245
width={190}
211246
alt='try bowtie'

0 commit comments

Comments
 (0)