diff --git a/package.json b/package.json index dce2117..9278cae 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,15 @@ "lint": "next lint" }, "dependencies": { + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", "framer-motion": "^10.18.0", "next": "14.0.4", "react": "^18", "react-dom": "^18", "react-icons": "^4.12.0", - "shiki": "^0.14.7" + "shiki": "^0.14.7", + "tailwind-merge": "^2.2.1" }, "devDependencies": { "@types/node": "^20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb42a11..eefc183 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,12 @@ settings: excludeLinksFromLockfile: false dependencies: + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + clsx: + specifier: ^2.1.0 + version: 2.1.0 framer-motion: specifier: ^10.18.0 version: 10.18.0(react-dom@18.2.0)(react@18.2.0) @@ -23,6 +29,9 @@ dependencies: shiki: specifier: ^0.14.7 version: 0.14.7 + tailwind-merge: + specifier: ^2.2.1 + version: 2.2.1 devDependencies: '@types/node': @@ -70,7 +79,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 - dev: true /@emotion/is-prop-valid@0.8.8: resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} @@ -699,10 +707,26 @@ packages: fsevents: 2.3.3 dev: true + /class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + dependencies: + clsx: 2.0.0 + dev: false + /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false + /clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + dev: false + + /clsx@2.1.0: + resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} + engines: {node: '>=6'} + dev: false + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2284,7 +2308,6 @@ packages: /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: true /regexp.prototype.flags@1.5.1: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} @@ -2568,6 +2591,12 @@ packages: engines: {node: '>= 0.4'} dev: true + /tailwind-merge@2.2.1: + resolution: {integrity: sha512-o+2GTLkthfa5YUt4JxPfzMIpQzZ3adD1vLVkvKE1Twl9UAhGsEbIZhHHZVRttyW177S8PDJI3bTQNaebyofK3Q==} + dependencies: + '@babel/runtime': 7.23.7 + dev: false + /tailwindcss@3.4.0: resolution: {integrity: sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==} engines: {node: '>=14.0.0'} diff --git a/src/app/docs/installation/page.tsx b/src/app/docs/installation/page.tsx new file mode 100644 index 0000000..3195689 --- /dev/null +++ b/src/app/docs/installation/page.tsx @@ -0,0 +1,59 @@ +import Pagination from "@/components/Pagination"; + + +export default function Installation() { + return ( + <> +

Installation

+ +
+ These React components use{' '} + + tailwind + {' '} + for styling and{' '} + + react-icons + {' '} + for icons (feel free to use any other icons), so make sure you got them + installed. +
+
+ Now choose any component you find useful, copy it to your project and + adjust it so it fulfills your needs. +
+
+ Keep in mind that these are YOUR components. It's on you to make + them more reusable and accessible. I created this to help you get + started with neobrutalism.{' '} + + This video + {' '} + can help you with creating more reusable components. +
+ + + + ) +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 0dd5744..da6fc06 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ "use client" -import Button from '@/components/neobruu/Button' +import {Button} from '@/components/neobruu/Button' import Image from 'next/image' import { IoLogoGithub } from 'react-icons/io' import { motion } from 'framer-motion' @@ -21,7 +21,7 @@ export default function Home() {
- diff --git a/src/components/CopyCode.tsx b/src/components/CopyCode.tsx index 6d80fbe..7ccfcaf 100644 --- a/src/components/CopyCode.tsx +++ b/src/components/CopyCode.tsx @@ -1,6 +1,6 @@ 'use client' import React, { useState } from 'react' -import Button from './neobruu/Button' +import { Button } from './neobruu/Button' import { IoCheckmark, IoCopy } from 'react-icons/io5' export default function CopyCode({ code }: { code: string }) { diff --git a/src/components/MobileSidebar.tsx b/src/components/MobileSidebar.tsx index 200b5fb..10565df 100644 --- a/src/components/MobileSidebar.tsx +++ b/src/components/MobileSidebar.tsx @@ -5,7 +5,6 @@ import Link from "next/link"; import { HiMenuAlt2 } from "react-icons/hi" import components from "@/data/components"; import Drawer from "@/components/neobruu/Drawer"; -import Button from "@/components/neobruu/Button"; import Badge from "@/components/neobruu/Badge"; export default function MobileSidebar() { diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx index 94fe01e..b695f4c 100644 --- a/src/components/Pagination.tsx +++ b/src/components/Pagination.tsx @@ -1,7 +1,7 @@ 'use client' import { useRouter } from 'next/navigation' import { FaChevronLeft, FaChevronRight } from 'react-icons/fa' -import Button from './neobruu/Button' +import {Button} from '@/components/neobruu/Button' type Props = { prev?: { @@ -30,7 +30,7 @@ export default function Pagination({ prev, next }: Props) { return (
{prev?.name && ( - - + +
) diff --git a/src/components/example/CardExample.tsx b/src/components/example/CardExample.tsx index 0b92e24..e6d1cef 100644 --- a/src/components/example/CardExample.tsx +++ b/src/components/example/CardExample.tsx @@ -1,5 +1,5 @@ import Card from "@/components/neobruu/Card"; -import Button from "@/components/neobruu/Button"; +import { Button } from "@/components/neobruu/Button"; export default function CardExample() { return ( diff --git a/src/components/example/DialogExample.tsx b/src/components/example/DialogExample.tsx index 678ab3b..7e77f20 100644 --- a/src/components/example/DialogExample.tsx +++ b/src/components/example/DialogExample.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import Dialog from "@/components/neobruu/Dialog"; -import Button from "@/components/neobruu/Button"; +import {Button} from "@/components/neobruu/Button"; export default function DialogExample() { const [showDialog, setShowDialog] = useState(false); @@ -26,7 +26,7 @@ export default function DialogExample() {
- +
diff --git a/src/components/example/DrawerExample.tsx b/src/components/example/DrawerExample.tsx index 63f4d5d..0b902fd 100644 --- a/src/components/example/DrawerExample.tsx +++ b/src/components/example/DrawerExample.tsx @@ -1,7 +1,7 @@ 'use client' import Drawer from "@/components/neobruu/Drawer"; import { useState } from "react"; -import Button from "@/components/neobruu/Button"; +import { Button } from "@/components/neobruu/Button"; export default function DrawerExample() { const [isDrawerOpen, setIsDrawerOpen] = useState(false); diff --git a/src/components/example/ToastExample.tsx b/src/components/example/ToastExample.tsx index f371b5f..f05e93c 100644 --- a/src/components/example/ToastExample.tsx +++ b/src/components/example/ToastExample.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { HiExclamation } from "react-icons/hi" import Toast from "@/components/neobruu/Toast"; -import Button from "@/components/neobruu/Button"; +import {Button} from "@/components/neobruu/Button"; export default function ToastExample() { const [showToast, setShowToast] = useState(false); diff --git a/src/components/example/TooltipExample.tsx b/src/components/example/TooltipExample.tsx index a6043a6..fa181eb 100644 --- a/src/components/example/TooltipExample.tsx +++ b/src/components/example/TooltipExample.tsx @@ -1,5 +1,5 @@ import Tooltip from "@/components/neobruu/Tooltip"; -import Button from "@/components/neobruu/Button"; +import { Button } from "@/components/neobruu/Button"; export default function TooltipExample() { @@ -7,7 +7,7 @@ export default function TooltipExample() {
- + Hello World! diff --git a/src/components/neobruu/Alert.tsx b/src/components/neobruu/Alert.tsx index 3217179..8b3d5d5 100644 --- a/src/components/neobruu/Alert.tsx +++ b/src/components/neobruu/Alert.tsx @@ -1,57 +1,66 @@ 'use client' -import { useState } from 'react'; -import { IoClose } from 'react-icons/io5' +import React, { useState } from 'react'; +import { IoClose } from 'react-icons/io5'; +import { VariantProps, cva } from 'class-variance-authority'; type Props = { children: React.ReactNode; icon: React.JSX.Element; - variant: 'primary' | 'secondary' | 'light' | 'dark' | 'blue'; - rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'; }; -export default function Alert({ +const alertVariants = cva( + 'w-full flex justify-between cursor-pointer items-center gap-3 text-sm md:text-base shadow-[2px_2px_0px_0px] px-4 md:px-5 py-2 md:py-3 transition-all', + { + variants: { + variant: { + primary: 'border-orange-700 bg-orange-300/85 text-zinc-800', + secondary: 'border-pink-700 bg-pink-400/85 text-zinc-800', + light: 'border-slate-300 bg-slate-50/85 text-zinc-800', + dark: 'border-zinc-900 bg-zinc-800/85 text-white', + blue: 'border-blue-500 bg-blue-400 text-zinc-800', + }, + rounded: { + none: 'rounded-none', + sm: 'rounded-sm', + md: 'rounded-md', + lg: 'rounded-lg', + xl: 'rounded-xl', + full: 'rounded-full', + }, + }, + defaultVariants: { + variant: 'primary', + rounded: 'none', + }, + } +); + +const Alert: React.FC> = ({ children, icon, variant = 'primary', - rounded = 'none' -}: Props) { - + rounded = 'none', + ...props +}) => { const [visible, setVisible] = useState(true); const handleAlert = () => { setVisible(!visible); - } + }; - const getColors = () => { - switch (variant) { - case 'primary': - return 'border-orange-700 bg-orange-300/85 text-zinc-800'; - case 'secondary': - return 'border-pink-700 bg-pink-400/85 text-zinc-800'; - case 'light': - return 'border-slate-300 bg-slate-50/85 text-zinc-800'; - case 'dark': - return 'border-zinc-900 bg-zinc-800/85 text-white'; - case 'blue': - return 'border-blue-500 bg-blue-400 text-zinc-800'; - default: - return 'border-orange-500 bg-orange-400 text-zinc-800'; - } - } + if (!visible) return null; return ( - visible && ( -
- - {icon} - +
+
+ {icon} {children} -
- ) + +
); -} +}; + +export default Alert; diff --git a/src/components/neobruu/Badge.tsx b/src/components/neobruu/Badge.tsx index c747a2a..5fb8b3c 100644 --- a/src/components/neobruu/Badge.tsx +++ b/src/components/neobruu/Badge.tsx @@ -1,42 +1,51 @@ -'use client' +import React from 'react'; +import { VariantProps, cva } from 'class-variance-authority'; + type Props = { text: string; - variant?: 'primary' | 'secondary' | 'light' | 'dark' | 'blue' | 'yellow' | 'green' | 'red' ; - rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'; }; -export default function Badge({ - text, - variant = 'primary', - rounded = 'none' -}: Props) { - const getColors = () => { - switch (variant) { - case 'primary': - return 'border-orange-700 bg-orange-400/70'; - case 'secondary': - return 'border-pink-700 bg-pink-500/70'; - case 'light': - return 'text-zinc-900 border-slate-300 bg-slate-50/70'; - case 'dark': - return 'border-zinc-900 bg-zinc-700/70 text-white'; - case 'blue': - return 'border-blue-700 bg-blue-500/70'; - case 'yellow': - return 'border-[#d3aa2f] bg-[#f7cb46]/70'; - case 'green': - return 'border-green-700 bg-green-500/70'; - case 'red': - return 'border-red-700 bg-red-500/70'; - default: - return 'border-orange-700 bg-orange-500/70'; - } +const badgeVariants = cva( + 'flex cursor-default items-center text-xs font-medium hover:shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] transition-all', + { + variants: { + variant: { + default: 'border-orange-700 bg-orange-400/70', + pink: 'border-pink-700 bg-pink-500/70', + light: 'text-zinc-900 border-slate-300 bg-slate-50/70', + dark: 'border-zinc-900 bg-zinc-700/70 text-white', + blue: 'border-blue-700 bg-blue-500/70', + yellow: 'border-[#d3aa2f] bg-[#f7cb46]/70', + green: 'border-green-700 bg-green-500/70', + red: 'border-red-700 bg-red-500/70', + }, + rounded: { + none: 'rounded-none', + sm: 'rounded-sm', + md: 'rounded-md', + lg: 'rounded-lg', + xl: 'rounded-xl', + full: 'rounded-full', + }, + }, + defaultVariants: { + variant: 'default', + rounded: 'none', + }, } +); + +const Badge: React.FC> = ({ + text, + variant = 'default', + rounded = 'none', + ...props +}) => { return ( - + {text} ); -} +}; + +export default Badge; diff --git a/src/components/neobruu/Button.tsx b/src/components/neobruu/Button.tsx index 308d40d..952814e 100644 --- a/src/components/neobruu/Button.tsx +++ b/src/components/neobruu/Button.tsx @@ -1,47 +1,79 @@ 'use client' -type Props = { - children: React.ReactNode; - onClick?: (event: React.MouseEvent) => void; - variant?: 'primary' | 'secondary' | 'light' | 'dark' | 'blue' | 'yellow' | 'green' | 'red' ; - rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'; -}; +import * as React from 'react' +import Link from 'next/link' +import { VariantProps, cva } from 'class-variance-authority' -export default function Button({ - children, - onClick, - variant = 'primary', - rounded = 'none' -}: Props) { - const getColors = () => { - switch (variant) { - case 'primary': - return 'border-black bg-orange-400'; - case 'secondary': - return 'border-black bg-pink-500'; - case 'light': - return 'border-black bg-slate-50'; - case 'dark': - return 'border-white/10 bg-zinc-900 text-white'; - case 'blue': - return 'border-black bg-blue-500'; - case 'yellow': - return 'border-black bg-[#f7cb46]'; - case 'green': - return 'border-black bg-green-500'; - case 'red': - return 'border-black bg-red-500'; - default: - return 'border-black bg-orange-500'; - } +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] transition-all active:translate-x-[3px] active:translate-y-[3px] active:shadow-none', + { + variants: { + variant: { + default: + 'bg-orange-400', + red: + 'bg-red-500', + blue: + 'bg-blue-500', + green: + 'bg-green-500', + yellow: + 'bg-[#f7cb46]', + pink: 'bg-pink-500', + dark: 'bg-zinc-900' + }, + size: { + default: 'h-10 py-2 px-4', + sm: 'h-9 px-2 rounded-md', + lg: 'h-11 px-8 rounded-md', + }, + rounded: { + none: 'rounded-none', + sm: 'rounded-sm', + md: 'rounded-md', + lg: 'rounded-lg', + xl: 'rounded-xl', + full: 'rounded-full' + } + }, + defaultVariants: { + variant: 'default', + size: 'default', + rounded: 'none' + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + href?: string +} + +const Button = React.forwardRef( + ({ className, children, href, variant, size, ...props }, ref) => { + if (href) { + return ( + + {children} + + ) } return ( - - ); -} + + ) + } +) +Button.displayName = 'Button' + +export { Button, buttonVariants } \ No newline at end of file diff --git a/src/data/components.ts b/src/data/components.ts index 7763fbc..394e37d 100644 --- a/src/data/components.ts +++ b/src/data/components.ts @@ -1,7 +1,7 @@ import Accordion from '@/components/neobruu/Accordion' import Alert from '@/components/neobruu/Alert' import Avatar from '@/components/neobruu/Avatar' -import Button from '@/components/neobruu/Button' +import {Button} from '@/components/neobruu/Button' import Badge from '@/components/neobruu/Badge' import Card from '@/components/neobruu/Card' import Checkbox from '@/components/neobruu/Checkbox' diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..4297d12 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} \ No newline at end of file