Skip to content

Commit

Permalink
(chore/ui): Add Badge, Feedback, Image Card
Browse files Browse the repository at this point in the history
  • Loading branch information
N3v1 committed Nov 30, 2024
1 parent d5ee324 commit 61a8c10
Show file tree
Hide file tree
Showing 25 changed files with 765 additions and 72 deletions.
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"format": "prettier --write ."
},
"dependencies": {
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"dompurify": "^3.2.2",
Expand All @@ -35,7 +36,11 @@
"typescript": "^5"
},
"compilerOptions": {
"typeRoots": ["node_modules/@types"],
"types": ["dompurify"]
"typeRoots": [
"node_modules/@types"
],
"types": [
"dompurify"
]
}
}
106 changes: 106 additions & 0 deletions src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React from 'react';
import { ReactNode } from 'react';

/**
* Enum representing the available variants for the Badge component.
* The variants control the badge's appearance and color.
*/
export enum BadgeVariant {
default = "default",
error = "error",
success = "success",
warning = "warning",
reference = "reference",
}

/**
* Props interface for the Badge component.
* Defines the expected properties that can be passed into the component.
*/
interface BadgeProps {

/**
* The content to be displayed inside the badge.
* It can be text, icons, or other React components.
*/
children: ReactNode;

/**
* Defines the style variant for the badge.
* Controls the background and text color (default: 'default').
*/
variant?: BadgeVariant;

/**
* Custom background color for the badge.
* Can be a Tailwind CSS color class or custom class.
*/
background?: string;

/**
* Custom text color for the badge.
* Can be a Tailwind CSS color class or custom class.
*/
textColor?: string;

/**
* Additional custom CSS classes to apply to the badge element.
* This is useful for applying further customization beyond the predefined styles.
*/
className?: string;
}

/**
* Badge component that renders a styled badge with text or other content.
* The badge's appearance is controlled via variants, background, and text color.
*
* @param {BadgeProps} props - The props for the Badge component.
* @returns {JSX.Element} The rendered Badge component.
*/
const Badge: React.FC<BadgeProps> = ({
children,
variant = BadgeVariant.default,
background = 'bg-gray-200',
textColor = 'text-gray-800',
className = '',
}) => {

/**
* A mapping of badge variants to their corresponding background and text color styles.
* This is used to determine the appearance of the badge based on the `variant` prop.
*/
const variantStyles: Record<
BadgeVariant,
{ background: string; textColor: string }
> = {
[BadgeVariant.default]: { background: 'bg-shark-800', textColor: 'text-shark-400' },
[BadgeVariant.error]: { background: 'bg-tamarind-800', textColor: 'text-tamarind-400' },
[BadgeVariant.success]: { background: 'bg-bush-800', textColor: 'text-bush-400' },
[BadgeVariant.warning]: { background: 'bg-clinker-800', textColor: 'text-clinker-400' },
[BadgeVariant.reference]: { background: 'bg-deep-teal-800', textColor: 'text-deep-teal-400' },
};

/**
* Determine the resolved background color for the badge.
* If a custom background is provided, it overrides the default variant background.
*/
const resolvedBackground = background !== 'bg-gray-200' ? background : variantStyles[variant].background;

/**
* Determine the resolved text color for the badge.
* If a custom text color is provided, it overrides the default variant text color.
*/
const resolvedTextColor = textColor !== 'text-gray-800' ? textColor : variantStyles[variant].textColor;

/**
* Renders the badge as a span element with dynamic classes for styling.
* The classes include padding, rounded corners, font size, and the background and text colors determined above.
*/
return(
<span className={`inline-block px-3 py-1 rounded-full text-sm font-semibold ${resolvedBackground} ${resolvedTextColor} ${className}`}>
{children}
</span>
);
};

export { Badge };
30 changes: 15 additions & 15 deletions src/components/ui/barchart-example-one.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';

export default function BarchartExampleOne() {
const data = [
{ name: 'Page A', uv: 400, pv: 2400, amt: 2400 },
{ name: 'Page B', uv: 300, pv: 1398, amt: 2210 },
{ name: 'Page C', uv: 200, pv: 9800, amt: 2290 },
];
const data = [
{ name: 'Page A', uv: 400, pv: 2400, amt: 2400 },
{ name: 'Page B', uv: 300, pv: 1398, amt: 2210 },
{ name: 'Page C', uv: 200, pv: 9800, amt: 2290 },
];

return(
<BarChart width={500} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="uv" fill="#8884d8" />
</BarChart>
);
}
return (
<BarChart width={500} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="uv" fill="#8884d8" />
</BarChart>
);
}
56 changes: 56 additions & 0 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';

import { cn } from '@/lib/utils';

const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';

export { Button, buttonVariants };
88 changes: 88 additions & 0 deletions src/components/ui/feedback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use client';

import React, { useState } from 'react';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';

type FeedbackOption = {
id: number;
emoji: string;
label: string;
};

const feedbackOptions: FeedbackOption[] = [
{ id: 1, emoji: '🤩', label: 'Very helpful' },
{ id: 2, emoji: '🙂', label: 'Helpful' },
{ id: 3, emoji: '🙁', label: 'Not helpful' },
{ id: 4, emoji: '😭', label: 'Very unhelpful' },
];

const Feedback: React.FC = () => {
const [selected, setSelected] = useState<number | null>(null);
const [feedback, setFeedback] = useState<string>('');

const handleSelect = (id: number) => {
setSelected((prevSelected) => (prevSelected === id ? null : id));
};

const handleSubmit = () => {
setSelected(null);
setFeedback('');
};

return (
<div className="flex items-center justify-center">
<div className="flex flex-col gap-4 px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-3xl text-neutral-500 dark:text-neutral-400">
{/* Feedback Selection */}
<div className="flex items-center gap-4">
<span className="text-sm flex items-center">Was this helpful?</span>
{feedbackOptions.map((option) => (
<button
key={option.id}
onClick={() => handleSelect(option.id)}
style={{
border: 'none',
background: 'none',
cursor: 'pointer',
fontSize: '20px',
opacity: selected === option.id ? 1 : 0.6,
transition: 'opacity 0.2s',
}}
aria-label={option.label}
>
{option.emoji}
</button>
))}
</div>

{selected && (
<div
className={`transition-all duration-300 overflow-hidden ${selected ? 'opacity-100 visible' : 'opacity-0 invisible'}`}
>
{/* Extended Feedback Form */}
{selected && (
<div className="flex flex-col gap-2 py-2">
<Textarea
placeholder="Type your feedback here."
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
/>

<div className="border-t w-full border-neutral-200 dark:border-neutral-800 my-2"></div>

<Button
className="font-semibold hover:bg-orange-500 hover:text-white transition"
onClick={handleSubmit}
>
Send
</Button>
</div>
)}
</div>
)}
</div>
</div>
);
};

export default Feedback;
2 changes: 1 addition & 1 deletion src/components/ui/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function Footer() {
];

return (
<footer className="bg-gray-100 dark:bg-neutral-900 text-foreground mt-12 w-full">
<footer className="bg-gray-100 dark:bg-neutral-900 text-foreground w-full">
<div className="container mx-auto px-4 py-8">
{/* Quick Links Section */}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
Expand Down
Loading

0 comments on commit 61a8c10

Please sign in to comment.