Merge pull request 'Icon Card' (#35) from adilallo/component/IconCard into main
Reviewed-on: #35
This commit was merged in pull request #35.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import "../app/globals.css";
|
||||
import "./fonts.css";
|
||||
import { MessagesProvider } from "../app/contexts/MessagesContext";
|
||||
import messages from "../messages/en/index";
|
||||
|
||||
/** @type { import('@storybook/react').Preview } */
|
||||
const preview = {
|
||||
@@ -13,9 +15,11 @@ const preview = {
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<MessagesProvider messages={messages}>
|
||||
<div className="font-inter">
|
||||
<Story />
|
||||
</div>
|
||||
</MessagesProvider>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,6 +9,8 @@ import Progress from "../components/Progress";
|
||||
import Create from "../components/Create";
|
||||
import Input from "../components/Input";
|
||||
import InputWithCounter from "../components/InputWithCounter";
|
||||
import IconCard from "../components/IconCard";
|
||||
import { getAssetPath } from "../../lib/assetUtils";
|
||||
|
||||
export default function ComponentsPreview() {
|
||||
const [alertVisible, setAlertVisible] = useState({
|
||||
@@ -413,6 +415,34 @@ export default function ComponentsPreview() {
|
||||
</div>
|
||||
</Create>
|
||||
</section>
|
||||
|
||||
{/* IconCard Component Section */}
|
||||
<section className="space-y-[var(--spacing-scale-024)]">
|
||||
<h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]">
|
||||
IconCard Component
|
||||
</h2>
|
||||
|
||||
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]">
|
||||
<div className="flex flex-wrap gap-[var(--spacing-scale-024)]">
|
||||
<IconCard
|
||||
icon={
|
||||
<img
|
||||
src={getAssetPath("assets/Vector_WorkerCoop.svg")}
|
||||
alt=""
|
||||
className="w-[36px] h-[36px]"
|
||||
width="36"
|
||||
height="36"
|
||||
/>
|
||||
}
|
||||
title="Worker's cooperatives"
|
||||
description="Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations."
|
||||
onClick={() => {
|
||||
// IconCard clicked handler
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { IconCardView } from "./IconCard.view";
|
||||
import type { IconCardProps } from "./IconCard.types";
|
||||
|
||||
const IconCardContainer = memo<IconCardProps>(
|
||||
({ icon, title, description, className = "", onClick }) => {
|
||||
const handleClick = () => {
|
||||
if (onClick) onClick();
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
handleClick();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IconCardView
|
||||
icon={icon}
|
||||
title={title}
|
||||
description={description}
|
||||
className={className}
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
IconCardContainer.displayName = "IconCard";
|
||||
|
||||
export default IconCardContainer;
|
||||
@@ -0,0 +1,16 @@
|
||||
export interface IconCardProps {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface IconCardViewProps {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
className: string;
|
||||
onClick: () => void;
|
||||
onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import type { IconCardViewProps } from "./IconCard.types";
|
||||
|
||||
export function IconCardView({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
className,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
}: IconCardViewProps) {
|
||||
return (
|
||||
<div
|
||||
className={`border border-[var(--color-border-default-primary)] flex flex-col h-[350px] items-start justify-between p-[var(--measures-spacing-020)] relative w-[288px] bg-transparent cursor-pointer transition-all duration-200 hover:scale-[1.02] hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-content-default-brand-primary)] focus:ring-offset-2 ${className}`}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
aria-label={`${title}: ${description}`}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div className="shrink-0 w-[36px] h-[36px] flex items-center justify-center">
|
||||
{icon}
|
||||
</div>
|
||||
|
||||
{/* Title - Centered with auto space above and below */}
|
||||
<h3 className="font-inter font-normal text-[32px] leading-[36px] text-[var(--color-content-default-primary)] w-full">
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="font-inter font-medium text-[10px] leading-[14px] uppercase text-[var(--color-content-default-primary)] w-full">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./IconCard.container";
|
||||
export type { IconCardProps } from "./IconCard.types";
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */
|
||||
export interface InputWithCounterProps {
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
@@ -9,4 +8,3 @@ export interface InputWithCounterProps {
|
||||
className?: string;
|
||||
inputClassName?: string;
|
||||
}
|
||||
/* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */
|
||||
@@ -0,0 +1,20 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_18446_66225)">
|
||||
<mask id="mask0_18446_66225" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
||||
<path d="M24 0H0V24H24V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_18446_66225)">
|
||||
<path d="M18.7277 17.2723L13.4552 12L18.7277 6.72763L24 12L18.7277 17.2723ZM5.27237 17.2723L0 12L5.27237 6.72763L10.5447 12L5.27237 17.2723ZM12 24L6.72763 18.7277L12 13.4552L17.2723 18.7277L12 24ZM12 10.5447L6.72763 5.27237L12 0L17.2723 5.27237L12 10.5447Z" fill="url(#paint0_linear_18446_66225)"/>
|
||||
<path d="M18.7277 17.2723L13.4552 12L18.7277 6.72763L24 12L18.7277 17.2723ZM5.27237 17.2723L0 12L5.27237 6.72763L10.5447 12L5.27237 17.2723ZM12 24L6.72763 18.7277L12 13.4552L17.2723 18.7277L12 24ZM12 10.5447L6.72763 5.27237L12 0L17.2723 5.27237L12 10.5447Z" fill="#FFCFAD"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_18446_66225" x1="2.46" y1="1.92" x2="12" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ACAAFF"/>
|
||||
<stop offset="1" stop-color="#C0E8FF"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_18446_66225">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.0001 20.8485C2.92376 28.4333 -4.43331 21.0763 3.15149 12C-4.43331 2.92372 2.92376 -4.43325 12.0001 3.15145C21.0748 -4.43325 28.4336 2.92372 20.8488 12C28.4336 21.0693 21.0748 28.4333 12.0001 20.8485Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 332 B |
@@ -0,0 +1,15 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_18446_66221)">
|
||||
<mask id="mask0_18446_66221" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
||||
<path d="M24 0H0V24H24V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_18446_66221)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4.03739C11.1329 4.03739 10.4299 4.74034 10.4299 5.60748H6.39252C6.39252 2.51056 8.90308 0 12 0C15.097 0 17.6075 2.51056 17.6075 5.60748C17.6075 8.7044 15.097 11.2149 12 11.2149V7.17757C12.8671 7.17757 13.5701 6.47461 13.5701 5.60748C13.5701 4.74034 12.8671 4.03739 12 4.03739ZM19.9626 12C19.9626 11.1329 19.2596 10.4299 18.3925 10.4299V6.39252C21.4895 6.39252 24 8.90308 24 12C24 15.097 21.4895 17.6075 18.3925 17.6075C15.2956 17.6075 12.785 15.097 12.785 12H16.8224C16.8224 12.8671 17.5254 13.5701 18.3925 13.5701C19.2596 13.5701 19.9626 12.8671 19.9626 12ZM5.60748 13.5701C4.74034 13.5701 4.03739 12.8671 4.03739 12C4.03739 11.1329 4.74034 10.4299 5.60748 10.4299C6.47461 10.4299 7.17757 11.1329 7.17757 12H11.2149C11.2149 8.90308 8.7044 6.39252 5.60748 6.39252C2.51056 6.39252 0 8.90308 0 12C0 15.097 2.51056 17.6075 5.60748 17.6075V13.5701ZM12 19.9626C12.8671 19.9626 13.5701 19.2596 13.5701 18.3925H17.6075C17.6075 21.4895 15.097 24 12 24C8.90308 24 6.39252 21.4895 6.39252 18.3925C6.39252 15.2956 8.90308 12.785 12 12.785V16.8224C11.1329 16.8224 10.4299 17.5254 10.4299 18.3925C10.4299 19.2596 11.1329 19.9626 12 19.9626Z" fill="#B8DBFC"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_18446_66221">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_18446_66223)">
|
||||
<mask id="mask0_18446_66223" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
||||
<path d="M24 0H0V24H24V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_18446_66223)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0C3.3137 0 6 2.68628 6 6C2.6863 6 0 3.31372 0 0ZM12 0C8.6863 0 6 2.68628 6 6C2.6863 6 0 8.68628 0 12C0 15.3137 2.6863 18 6 18C2.6863 18 0 20.6863 0 24C3.3137 24 6 21.3137 6 18C6 21.3137 8.6863 24 12 24C15.3137 24 18 21.3137 18 18C18 21.3137 20.6863 24 24 24C24 20.6863 21.3137 18 18 18C21.3137 18 24 15.3137 24 12C24 8.68628 21.3137 6 18 6C21.3137 6 24 3.31372 24 0C20.6863 0 18 2.68628 18 6C18 2.68628 15.3137 0 12 0ZM18 18C18 14.6863 15.3137 12 12 12C12 15.3137 14.6863 18 18 18ZM12 12C15.3137 12 18 9.31372 18 6C18 9.31372 20.6863 12 24 12C20.6863 12 18 14.6863 18 18C14.6863 18 12 20.6863 12 24C12 20.6863 9.3137 18 6 18C9.3137 18 12 15.3137 12 12ZM12 12C8.6863 12 6 14.6863 6 18C6 14.6863 3.3137 12 0 12C3.3137 12 6 9.31372 6 6C6 9.31372 8.6863 12 12 12ZM12 12C12 8.68628 14.6863 6 18 6C14.6863 6 12 3.31372 12 0C12 3.31372 9.3137 6 6 6C9.3137 6 12 8.68628 12 12Z" fill="#E4FF97"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_18446_66223">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_18446_66222)">
|
||||
<mask id="mask0_18446_66222" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
||||
<path d="M24 0H0V24H24V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_18446_66222)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.3772 0C12.6422 0 12.8572 0.214903 12.8572 0.48V5.12099C12.8572 5.64857 13.5846 5.78996 13.7822 5.3008L15.5207 0.997747C15.62 0.751954 15.8998 0.633203 16.1456 0.73251L16.845 1.01507C17.0908 1.11438 17.2096 1.39414 17.1102 1.63993L15.2568 6.22738C15.0608 6.7122 15.6714 7.11635 16.0412 6.7466L19.5397 3.24803C19.7273 3.06058 20.0311 3.06058 20.2186 3.24803L20.752 3.78139C20.9394 3.96884 20.9394 4.27276 20.752 4.46021L17.6102 7.60193C17.2352 7.97698 17.6558 8.59354 18.1418 8.38118L22.2132 6.60235C22.4562 6.49621 22.7392 6.6071 22.8452 6.85002L23.1473 7.54122C23.2534 7.78415 23.1425 8.06711 22.8996 8.17325L18.2081 10.223C17.7314 10.4312 17.8801 11.1429 18.4002 11.1429H23.52C23.7851 11.1429 24 11.3578 24 11.6229V12.3772C24 12.6422 23.7851 12.8572 23.52 12.8572H18.4003C17.8801 12.8572 17.7316 13.5688 18.2081 13.777L22.8996 15.8268C23.1425 15.9329 23.2534 16.2158 23.1473 16.4587L22.8452 17.1499C22.7392 17.3929 22.4562 17.5038 22.2132 17.3976L18.1418 15.6188C17.6558 15.4064 17.2352 16.023 17.6102 16.3981L20.752 19.5397C20.9394 19.7273 20.9394 20.0311 20.752 20.2186L20.2186 20.752C20.0311 20.9394 19.7273 20.9394 19.5397 20.752L16.0412 17.2534C15.6714 16.8836 15.0608 17.2878 15.2568 17.7726L17.1102 22.3601C17.2096 22.6058 17.0908 22.8856 16.845 22.9849L16.1456 23.2675C15.8998 23.3668 15.62 23.2481 15.5207 23.0022L13.7822 18.6992C13.5846 18.21 12.8572 18.3515 12.8572 18.879V23.52C12.8572 23.7851 12.6422 24 12.3772 24H11.6229C11.3578 24 11.1429 23.7851 11.1429 23.52V18.879C11.1429 18.3515 10.4154 18.21 10.2178 18.6992L8.47926 23.0022C8.37995 23.2481 8.10019 23.3668 7.8544 23.2675L7.15504 22.9849C6.90924 22.8856 6.7905 22.6058 6.8898 22.3601L8.74326 17.7726C8.93915 17.2878 8.32855 16.8836 7.9588 17.2534L4.46022 20.752C4.27277 20.9394 3.96884 20.9394 3.7814 20.752L3.24804 20.2186C3.06059 20.0311 3.06059 19.7273 3.24804 19.5397L6.38975 16.398C6.7648 16.023 6.3442 15.4064 5.85816 15.6188L1.78674 17.3976C1.54382 17.5038 1.26085 17.3929 1.15472 17.1499L0.852728 16.4587C0.746593 16.2158 0.857482 15.9329 1.1004 15.8268L5.79188 13.777C6.26849 13.5688 6.11982 12.8572 5.5997 12.8572H0.48C0.214903 12.8572 0 12.6422 0 12.3772V11.6229C0 11.3578 0.214903 11.1429 0.48 11.1429H5.59973C6.11983 11.1429 6.26851 10.4312 5.79191 10.223L1.1004 8.17325C0.857478 8.06711 0.74659 7.78415 0.852725 7.54122L1.15471 6.85002C1.26085 6.6071 1.54381 6.49621 1.78674 6.60235L5.85816 8.38118C6.3442 8.59355 6.7648 7.97698 6.38975 7.60193L3.24804 4.46022C3.06059 4.27277 3.06059 3.96884 3.24804 3.78139L3.78139 3.24804C3.96884 3.06059 4.27277 3.06059 4.46022 3.24804L7.9588 6.74662C8.32854 7.11636 8.93914 6.71221 8.74326 6.22739L6.8898 1.63993C6.7905 1.39414 6.90925 1.11438 7.15504 1.01507L7.85441 0.73251C8.10019 0.633203 8.37996 0.751954 8.47926 0.997747L10.2178 5.3008C10.4154 5.78996 11.1429 5.64857 11.1429 5.12099V0.48C11.1429 0.214903 11.3578 0 11.6229 0H12.3772ZM12 17.1428C14.8403 17.1428 17.1428 14.8403 17.1428 12C17.1428 9.15968 14.8403 6.85715 12 6.85715C9.15968 6.85715 6.85715 9.15968 6.85715 12C6.85715 14.8403 9.15968 17.1428 12 17.1428Z" fill="url(#paint0_linear_18446_66222)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_18446_66222" x1="2.46" y1="1.92" x2="12" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ACAAFF"/>
|
||||
<stop offset="1" stop-color="#C0E8FF"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_18446_66222">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_18446_66224)">
|
||||
<mask id="mask0_18446_66224" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
||||
<path d="M24 0H0V24H24V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_18446_66224)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8572 0H11.1429V7.59037L8.29945 0.552698L6.70999 1.19488L9.62706 8.41487L4.12081 2.90862L2.90863 4.12081L8.18612 9.39829L1.34689 6.41017L0.660551 7.98107L7.89726 11.1429H0V12.8572H7.89725L0.660553 16.0189L1.34689 17.5898L8.18611 14.6017L2.90863 19.8792L4.12081 21.0913L9.62706 15.5851L6.70999 22.8052L8.29945 23.4473L11.1429 16.4096V24H12.8572V16.4096L15.7006 23.4473L17.29 22.8052L14.373 15.5851L19.8792 21.0913L21.0913 19.8792L15.8138 14.6017L22.6531 17.5898L23.3394 16.0189L16.1028 12.8572H24V11.1429H16.1027L23.3394 7.98107L22.6531 6.41017L15.8138 9.39829L21.0913 4.1208L19.8792 2.90862L14.373 8.41486L17.29 1.19488L15.7006 0.552698L12.8572 7.59037V0Z" fill="#E9B8FF"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_18446_66224">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +1,3 @@
|
||||
import React, { useState } from "react";
|
||||
import Create from "../app/components/Create";
|
||||
import Input from "../app/components/Input";
|
||||
|
||||
@@ -44,20 +43,10 @@ export default {
|
||||
};
|
||||
|
||||
const Template = (args) => {
|
||||
const [isOpen, setIsOpen] = useState(args.isOpen || false);
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
<button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded"
|
||||
>
|
||||
Open Create Dialog
|
||||
</button>
|
||||
<Create {...args} isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
||||
<Create {...args} isOpen={true} onClose={() => {}}>
|
||||
{args.children}
|
||||
</Create>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import IconCard from "../app/components/IconCard";
|
||||
import { getAssetPath } from "../lib/assetUtils";
|
||||
|
||||
export default {
|
||||
title: "Components/IconCard",
|
||||
component: IconCard,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"An interactive card component that displays an icon, title, and description. Features hover states, keyboard navigation, and accessibility support. Use Tab key to test focus indicators and Enter/Space to activate.",
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
icon: {
|
||||
control: false,
|
||||
description: "The icon element to display at the top of the card",
|
||||
},
|
||||
title: {
|
||||
control: { type: "text" },
|
||||
description: "The main title of the card",
|
||||
},
|
||||
description: {
|
||||
control: { type: "text" },
|
||||
description: "The description text displayed in uppercase",
|
||||
},
|
||||
onClick: { action: "clicked" },
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
// Worker's Coop icon
|
||||
const WorkerCoopIcon = () => (
|
||||
<img
|
||||
src={getAssetPath("assets/Vector_WorkerCoop.svg")}
|
||||
alt=""
|
||||
className="w-[36px] h-[36px]"
|
||||
width="36"
|
||||
height="36"
|
||||
/>
|
||||
);
|
||||
|
||||
export const Default = {
|
||||
args: {
|
||||
icon: <WorkerCoopIcon />,
|
||||
title: "Worker's cooperatives",
|
||||
description:
|
||||
"Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations.",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLongTitle = {
|
||||
args: {
|
||||
icon: <WorkerCoopIcon />,
|
||||
title: "This is a very long title that might wrap to multiple lines",
|
||||
description:
|
||||
"Employee-owned businesses often need to clarify how power is shared.",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithShortDescription = {
|
||||
args: {
|
||||
icon: <WorkerCoopIcon />,
|
||||
title: "Worker's cooperatives",
|
||||
description: "Short description",
|
||||
},
|
||||
};
|
||||
|
||||
export const Interactive = {
|
||||
args: {
|
||||
icon: <WorkerCoopIcon />,
|
||||
title: "Clickable Card",
|
||||
description: "This card has an onClick handler",
|
||||
onClick: () => {
|
||||
console.log("Card clicked!");
|
||||
},
|
||||
},
|
||||
};
|
||||
+17
-13
@@ -39,58 +39,62 @@ export const Default = {
|
||||
args: {
|
||||
progress: "3-2",
|
||||
},
|
||||
render: (args) => <Progress {...args} />,
|
||||
render: (args) => (
|
||||
<div className="w-full max-w-[600px]">
|
||||
<Progress {...args} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const AllStates = {
|
||||
args: {},
|
||||
render: (_args) => (
|
||||
<div className="space-y-4 w-full max-w-[600px]">
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">1-0</p>
|
||||
<Progress {..._args} progress="1-0" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">1-1</p>
|
||||
<Progress {..._args} progress="1-1" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">1-2</p>
|
||||
<Progress {..._args} progress="1-2" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">1-3</p>
|
||||
<Progress {..._args} progress="1-3" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">1-4</p>
|
||||
<Progress {..._args} progress="1-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">1-5</p>
|
||||
<Progress {..._args} progress="1-5" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">2-0</p>
|
||||
<Progress {..._args} progress="2-0" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">2-1</p>
|
||||
<Progress {..._args} progress="2-1" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">2-2</p>
|
||||
<Progress {..._args} progress="2-2" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">3-0</p>
|
||||
<Progress {..._args} progress="3-0" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">3-1</p>
|
||||
<Progress {..._args} progress="3-1" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full">
|
||||
<p className="text-white mb-2">3-2</p>
|
||||
<Progress {..._args} progress="3-2" />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import IconCard from "../../app/components/IconCard";
|
||||
import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
|
||||
type IconCardProps = React.ComponentProps<typeof IconCard>;
|
||||
|
||||
const baseProps: IconCardProps = {
|
||||
icon: <div data-testid="test-icon">Icon</div>,
|
||||
title: "Worker's cooperatives",
|
||||
description:
|
||||
"Employee-owned businesses often need to clarify how power is shared",
|
||||
};
|
||||
|
||||
const config: ComponentTestSuiteConfig<IconCardProps> = {
|
||||
component: IconCard,
|
||||
name: "IconCard",
|
||||
props: baseProps,
|
||||
requiredProps: ["icon", "title", "description"],
|
||||
optionalProps: {
|
||||
className: "custom-class",
|
||||
onClick: vi.fn(),
|
||||
},
|
||||
primaryRole: "button",
|
||||
testCases: {
|
||||
renders: true,
|
||||
accessibility: true,
|
||||
keyboardNavigation: true,
|
||||
disabledState: false,
|
||||
errorState: false,
|
||||
},
|
||||
};
|
||||
|
||||
componentTestSuite<IconCardProps>(config);
|
||||
|
||||
describe("IconCard (behavioral tests)", () => {
|
||||
it("calls onClick when clicked", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.click(card);
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls onClick when Enter key is pressed", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.keyDown(card, { key: "Enter" });
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls onClick when Space key is pressed", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.keyDown(card, { key: " " });
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders icon, title, and description", () => {
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div data-testid="icon">Icon</div>}
|
||||
title="Worker's cooperatives"
|
||||
description="Employee-owned businesses"
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
||||
expect(screen.getByText("Worker's cooperatives")).toBeInTheDocument();
|
||||
expect(screen.getByText("Employee-owned businesses")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("has proper ARIA label", () => {
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveAttribute("aria-label", "Test Title: Test Description");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user