Skip to content

Commit 5060cd1

Browse files
authored
Merge pull request #1409 from w3bdesign/develop
Improve design and refactor Produkter
2 parents 61d8754 + 7611005 commit 5060cd1

File tree

6 files changed

+158
-59
lines changed

6 files changed

+158
-59
lines changed

src/components/Cart/CartContents.component.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ const CartContents = () => {
136136
data.cart.contents.nodes,
137137
)
138138
}
139-
color="red"
139+
variant="secondary"
140140
buttonDisabled={updateCartProcessing}
141141
>
142142
Fjern
@@ -156,7 +156,7 @@ const CartContents = () => {
156156
{!isCheckoutPage && (
157157
<div className="flex justify-center mb-4">
158158
<Link href="/kasse" passHref>
159-
<Button fullWidth>GÅ TIL KASSE</Button>
159+
<Button variant="primary" fullWidth>GÅ TIL KASSE</Button>
160160
</Link>
161161
</div>
162162
)}
@@ -168,7 +168,7 @@ const CartContents = () => {
168168
Ingen produkter i handlekurven
169169
</h2>
170170
<Link href="/produkter" passHref>
171-
<Button>Fortsett å handle</Button>
171+
<Button variant="primary">Fortsett å handle</Button>
172172
</Link>
173173
</div>
174174
)}

src/components/Index/Hero.component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const Hero = () => (
2727
</h1>
2828
<Button
2929
href="/produkter"
30-
isHero
30+
variant="hero"
3131
>
3232
Se Utvalget
3333
</Button>

src/components/Product/ProductFilters.component.tsx

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Dispatch, SetStateAction } from 'react';
22
import { Product, ProductType } from '@/types/product';
3+
import Button from '@/components/UI/Button.component';
4+
import Checkbox from '@/components/UI/Checkbox.component';
5+
import RangeSlider from '@/components/UI/RangeSlider.component';
36

47
interface ProductFiltersProps {
58
selectedSizes: string[];
@@ -65,59 +68,48 @@ const ProductFilters = ({
6568

6669
return (
6770
<div className="w-full md:w-64 flex-shrink-0">
68-
<div className="bg-white p-8 sm:p-6 rounded-lg shadow-sm">
71+
<div className="bg-white px-8 pb-8 sm:px-6 sm:pb-6 rounded-lg shadow-sm">
6972
<div className="mb-8">
7073
<h3 className="font-semibold mb-4">PRODUKT TYPE</h3>
7174
<div className="space-y-2">
7275
{productTypes.map((type) => (
73-
<label key={type.id} className="flex items-center">
74-
<input
75-
type="checkbox"
76-
className="form-checkbox"
77-
checked={type.checked}
78-
onChange={() => toggleProductType(type.id)}
79-
/>
80-
<span className="ml-2">{type.name}</span>
81-
</label>
76+
<Checkbox
77+
key={type.id}
78+
id={type.id}
79+
label={type.name}
80+
checked={type.checked}
81+
onChange={() => toggleProductType(type.id)}
82+
/>
8283
))}
8384
</div>
8485
</div>
8586

8687
<div className="mb-8">
8788
<h3 className="font-semibold mb-4">PRIS</h3>
88-
<label htmlFor="price-range" className="sr-only">Pris</label>
89-
<input
90-
id="price-range"
91-
type="range"
92-
min="0"
93-
max="1000"
94-
value={priceRange[1]}
95-
onChange={(e) =>
96-
setPriceRange([priceRange[0], parseInt(e.target.value)])
97-
}
98-
className="w-full"
99-
/>
100-
<div className="flex justify-between mt-2">
101-
<span>kr {priceRange[0]}</span>
102-
<span>kr {priceRange[1]}</span>
103-
</div>
89+
<RangeSlider
90+
id="price-range"
91+
label="Pris"
92+
min={0}
93+
max={1000}
94+
value={priceRange[1]}
95+
startValue={priceRange[0]}
96+
onChange={(value) => setPriceRange([priceRange[0], value])}
97+
formatValue={(value) => `kr ${value}`}
98+
/>
10499
</div>
105100

106101
<div className="mb-8">
107102
<h3 className="font-semibold mb-4">STØRRELSE</h3>
108103
<div className="grid grid-cols-3 gap-2">
109104
{sizes.map((size) => (
110-
<button
105+
<Button
111106
key={size}
112-
onClick={() => toggleSize(size)}
113-
className={`px-3 py-1 border rounded ${
114-
selectedSizes.includes(size)
115-
? 'bg-gray-900 text-white'
116-
: 'hover:bg-gray-100'
117-
}`}
107+
handleButtonClick={() => toggleSize(size)}
108+
variant="filter"
109+
selected={selectedSizes.includes(size)}
118110
>
119111
{size}
120-
</button>
112+
</Button>
121113
))}
122114
</div>
123115
</div>
@@ -142,12 +134,12 @@ const ProductFilters = ({
142134
</div>
143135
</div>
144136

145-
<button
146-
onClick={resetFilters}
147-
className="w-full mt-8 py-2 px-4 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors"
137+
<Button
138+
handleButtonClick={resetFilters}
139+
variant="reset"
148140
>
149141
Resett filter
150-
</button>
142+
</Button>
151143
</div>
152144
</div>
153145
);

src/components/UI/Button.component.tsx

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,62 @@
11
import { ReactNode } from 'react';
22
import Link from 'next/link';
33

4-
type TButtonColors = 'red' | 'blue';
4+
type TButtonVariant = 'primary' | 'secondary' | 'hero' | 'filter' | 'reset';
55

66
interface IButtonProps {
77
handleButtonClick?: () => void;
88
buttonDisabled?: boolean;
9-
color?: TButtonColors;
10-
children: ReactNode;
9+
variant?: TButtonVariant;
10+
children?: ReactNode;
1111
fullWidth?: boolean;
12-
isHero?: boolean;
1312
href?: string;
13+
title?: string;
14+
selected?: boolean;
1415
}
1516

1617
/**
1718
* Renders a clickable button
1819
* @function Button
1920
* @param {void} handleButtonClick - Handle button click
2021
* @param {boolean?} buttonDisabled - Is button disabled?
21-
* @param {TButtonColors?} color - Color for button, either red or blue
22+
* @param {TButtonVariant?} variant - Button variant
2223
* @param {ReactNode} children - Children for button
2324
* @param {boolean?} fullWidth - Whether the button should be full-width on mobile
25+
* @param {boolean?} selected - Whether the button is in a selected state
2426
* @returns {JSX.Element} - Rendered component
2527
*/
2628
const Button = ({
2729
handleButtonClick,
2830
buttonDisabled,
29-
color = 'blue',
31+
variant = 'primary',
3032
children,
3133
fullWidth = false,
32-
isHero = false,
3334
href,
35+
title,
36+
selected = false,
3437
}: IButtonProps) => {
35-
const getColorClasses = (buttonColor: TButtonColors) => {
36-
if (buttonColor === 'blue') {
37-
return 'bg-blue-500 hover:bg-blue-600';
38+
const getVariantClasses = (variant: TButtonVariant = 'primary') => {
39+
switch (variant) {
40+
case 'hero':
41+
return 'inline-block px-8 py-4 text-sm tracking-wider uppercase bg-white text-gray-900 hover:bg-gray-400 hover:text-white hover:shadow-md';
42+
case 'filter':
43+
return selected
44+
? 'px-3 py-1 border rounded bg-gray-900 text-white'
45+
: 'px-3 py-1 border rounded hover:bg-gray-100 bg-white text-gray-900';
46+
case 'reset':
47+
return 'w-full mt-8 py-2 px-4 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors';
48+
case 'secondary':
49+
return 'px-2 lg:px-4 py-2 font-bold border border-gray-400 border-solid rounded text-white bg-red-500 hover:bg-red-600';
50+
default: // primary
51+
return 'px-2 lg:px-4 py-2 font-bold border border-gray-400 border-solid rounded text-white bg-blue-500 hover:bg-blue-600';
3852
}
39-
return 'bg-red-500 hover:bg-red-600';
4053
};
4154

42-
const buttonClasses = isHero
43-
? 'inline-block px-8 py-4 text-sm tracking-wider uppercase bg-white text-gray-900 hover:bg-gray-400 hover:text-white hover:shadow-md'
44-
: `px-2 lg:px-4 py-2 font-bold border border-gray-400 border-solid rounded text-white ${getColorClasses(color)}`;
45-
46-
const classes = `${buttonClasses} ease-in-out transition-all duration-300 disabled:opacity-50 ${
55+
const classes = `${getVariantClasses(variant)} ease-in-out transition-all duration-300 disabled:opacity-50 ${
4756
fullWidth ? 'w-full md:w-auto' : ''
4857
}`;
4958

50-
if (href && isHero) {
59+
if (href) {
5160
return (
5261
<Link href={href} className={classes}>
5362
{children}
@@ -60,6 +69,7 @@ const Button = ({
6069
onClick={handleButtonClick}
6170
disabled={buttonDisabled}
6271
className={classes}
72+
title={title}
6373
>
6474
{children}
6575
</button>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ChangeEvent } from 'react';
2+
3+
interface ICheckboxProps {
4+
id: string;
5+
label: string;
6+
checked: boolean;
7+
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
8+
}
9+
10+
/**
11+
* A reusable checkbox component with a label
12+
* @function Checkbox
13+
* @param {string} id - Unique identifier for the checkbox
14+
* @param {string} label - Label text to display next to the checkbox
15+
* @param {boolean} checked - Whether the checkbox is checked
16+
* @param {function} onChange - Handler for when the checkbox state changes
17+
* @returns {JSX.Element} - Rendered component
18+
*/
19+
const Checkbox = ({ id, label, checked, onChange }: ICheckboxProps) => {
20+
return (
21+
<label htmlFor={id} className="flex items-center py-2 cursor-pointer">
22+
<input
23+
id={id}
24+
type="checkbox"
25+
className="form-checkbox h-5 w-5 cursor-pointer"
26+
checked={checked}
27+
onChange={onChange}
28+
/>
29+
<span className="ml-3 text-base">{label}</span>
30+
</label>
31+
);
32+
};
33+
34+
export default Checkbox;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { ChangeEvent } from 'react';
2+
3+
interface IRangeSliderProps {
4+
id: string;
5+
label: string;
6+
min: number;
7+
max: number;
8+
value: number;
9+
onChange: (value: number) => void;
10+
startValue?: number;
11+
formatValue?: (value: number) => string;
12+
}
13+
14+
/**
15+
* A reusable range slider component with labels
16+
* @function RangeSlider
17+
* @param {string} id - Unique identifier for the slider
18+
* @param {string} label - Accessible label for the slider
19+
* @param {number} min - Minimum value of the range
20+
* @param {number} max - Maximum value of the range
21+
* @param {number} value - Current value of the slider
22+
* @param {function} onChange - Handler for when the slider value changes
23+
* @param {number} startValue - Optional starting value to display (defaults to min)
24+
* @param {function} formatValue - Optional function to format the displayed values
25+
* @returns {JSX.Element} - Rendered component
26+
*/
27+
const RangeSlider = ({
28+
id,
29+
label,
30+
min,
31+
max,
32+
value,
33+
onChange,
34+
startValue = min,
35+
formatValue = (val: number) => val.toString(),
36+
}: IRangeSliderProps) => {
37+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
38+
onChange(parseInt(e.target.value));
39+
};
40+
41+
return (
42+
<div>
43+
<label htmlFor={id} className="sr-only">
44+
{label}
45+
</label>
46+
<input
47+
id={id}
48+
type="range"
49+
min={min}
50+
max={max}
51+
value={value}
52+
onChange={handleChange}
53+
className="w-full cursor-pointer"
54+
/>
55+
<div className="flex justify-between mt-2">
56+
<span>{formatValue(startValue)}</span>
57+
<span>{formatValue(value)}</span>
58+
</div>
59+
</div>
60+
);
61+
};
62+
63+
export default RangeSlider;

0 commit comments

Comments
 (0)