A collection of components made for Fresh.
import { JSX } from "preact";
import { IS_BROWSER } from "$fresh/runtime.ts";
export default function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
return (
<button
{...props}
disabled={!IS_BROWSER || props.disabled}
class={`px-3 py-2 bg-white rounded border(gray-500 2) hover:bg-gray-200 active:bg-gray-300 disabled:(opacity-50 cursor-not-allowed) ${
props.class ?? ""
}`}
/>
);
}
import { JSX } from "preact";
import { IS_BROWSER } from "$fresh/runtime.ts";
export default function ColoredButton(
props: JSX.HTMLAttributes<HTMLButtonElement>,
) {
return (
<button
{...props}
disabled={!IS_BROWSER || props.disabled}
class="px-3 py-2 bg-blue-200 text-blue-800 rounded hover:bg-blue-300 active:bg-blue-400"
/>
);
}
import { JSX } from "preact";
export default function LinkButton(
props: JSX.HTMLAttributes<HTMLAnchorElement>,
) {
return (
<a
{...props}
class={`inline-block cursor-pointer px-3 py-2 bg-white rounded hover:bg-gray-100 ${
props.class ?? ""
}`}
/>
);
}
import { JSX } from "preact";
import { IS_BROWSER } from "$fresh/runtime.ts";
export default function Input(props: JSX.HTMLAttributes<HTMLInputElement>) {
return (
<input
{...props}
disabled={!IS_BROWSER || props.disabled}
class={`px-3 py-2 bg-white rounded border(gray-500 2) disabled:(opacity-50 cursor-not-allowed) ${
props.class ?? ""
}`}
/>
);
}
import LemonIcon from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/lemon-2.tsx";
type Props = {
active: string;
};
export default function Header({ active }: Props) {
const menus = [
{ name: "Home", href: "/" },
{ name: "Components", href: "/components" },
{ name: "Docs", href: "/docs" },
];
return (
<div class="bg-white w-full max-w-screen-lg py-6 px-8 flex flex-col md:flex-row gap-4">
<div class="flex items-center flex-1">
<LemonIcon />
<div class="text-2xl ml-1 font-bold">
Fresh
</div>
</div>
<ul class="flex items-center gap-6">
{menus.map((menu) => (
<li>
<a
href={menu.href}
class={"text-gray-500 hover:text-gray-700 py-1 border-gray-500" +
(menu.href === active ? " font-bold border-b-2" : "")}
>
{menu.name}
</a>
</li>
))}
</ul>
</div>
);
}
import { ComponentChildren } from "preact";
import LemonIcon from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/lemon-2.tsx";
import BrandGithub from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/brand-github.tsx";
type Props = {
children: ComponentChildren;
};
export default function Footer({ children }: Props) {
const menus = [
{
title: "Documentation",
children: [
{ name: "Getting Started", href: "#" },
{ name: "Guide", href: "#" },
{ name: "API", href: "#" },
{ name: "Showcase", href: "#" },
{ name: "Pricing", href: "#" },
],
},
{
title: "Community",
children: [
{ name: "Forum", href: "#" },
{ name: "Discord", href: "#" },
],
},
];
return (
<div class="bg-white flex flex-col md:flex-row w-full max-w-screen-lg gap-8 md:gap-16 px-8 py-8 text-sm">
<div class="flex-1">
<div class="flex items-center gap-1">
<LemonIcon class="inline-block" />
<div class="font-bold text-2xl">
Fresh
</div>
</div>
<div class="text-gray-500">
Full Stack Framework
</div>
</div>
{menus.map((item) => (
<div class="mb-4" key={item.title}>
<div class="font-bold">{item.title}</div>
<ul class="mt-2">
{item.children.map((child) => (
<li class="mt-2" key={child.name}>
<a
class="text-gray-500 hover:text-gray-700"
href={child.href}
>
{child.name}
</a>
</li>
))}
</ul>
</div>
))}
<div class="text-gray-500 space-y-2">
<div class="text-xs">
Copyright © 2020 DenoLand<br />
All right reserved.
</div>
<a
href="https://github.com/denoland/fresh"
class="inline-block hover:text-black"
>
<BrandGithub />
</a>
</div>
</div>
);
}
Fresh Components is a collection of components built with Preact and Tailwind CSS.
import IconChevronRight from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/chevron-right.tsx";
export default function Hero() {
return (
<div
class="w-full flex px-8 h-96 justify-center items-center flex-col gap-8 bg-cover bg-center bg-no-repeat bg-gray-100 rounded-xl text-white"
style="background-image:linear-gradient(rgba(0, 0, 40, 0.8),rgba(0, 0, 40, 0.8)), url('/gallery/hero-bg.webp');"
>
<div class="space-y-4 text-center">
<h1 class="text-4xl inline-block font-bold">Fresh Components Beta</h1>
<p class="text-xl max-w-lg text-blue-100">
Fresh Components is a collection of components built with Preact and
Tailwind CSS.
</p>
</div>
<div>
<a
href="#"
class="block mt-4 text-blue-500 cursor-pointer inline-flex items-center group text-blue-800 bg-white px-8 py-2 rounded-md hover:bg-blue-50 font-bold"
>
Sign Up{" "}
</a>
<a
href="#"
class="block mt-4 transition-colors text-blue-400 cursor-pointer inline-flex items-center group px-4 py-2 hover:text-blue-100"
>
Documentation{" "}
<IconChevronRight class="inline-block w-5 h-5 transition group-hover:translate-x-0.5" />
</a>
</div>
</div>
);
}
import IconAlarm from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/alarm.tsx";
import IconAirBalloon from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/air-balloon.tsx";
import IconArmchair from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/armchair.tsx";
import IconChevronRight from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/chevron-right.tsx";
export default function Features() {
const featureItems = [
{
icon: IconAlarm,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nunc ut aliquam aliquam",
link: "#",
},
{
icon: IconAirBalloon,
description:
"Nunc nisl aliquet nisl, eget aliquam nisl nisl sit amet lorem. Sed euismod, nunc ut aliquam aliquam, nunc nisl aliquet nisl, ",
},
{
icon: IconArmchair,
description: "Eget aliquam nisl nisl sit amet lorem.",
link: "#",
},
];
return (
<div class="flex flex-col md:flex-row gap-8 bg-white p-8">
{featureItems.map((item) => {
return (
<div class="flex-1 space-y-2">
<div class="bg-blue-600 inline-block p-3 rounded-xl text-white">
<item.icon class="w-10 h-10" />
</div>
<p class="text-xl">
{item.description}
</p>
{item.link &&
(
<a class="block" href={item.link}>
<p class="text-blue-500 cursor-pointer hover:underline inline-flex items-center group">
Read More{" "}
<IconChevronRight class="inline-block w-5 h-5 transition group-hover:translate-x-0.5" />
</p>
</a>
)}
</div>
);
})}
</div>
);
}
import { tw } from "twind";
import { asset } from "$fresh/runtime.ts";
import { useSignal } from "@preact/signals";
import { useEffect, useRef } from "preact/hooks";
import IconCircleChevronsRight from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/circle-chevrons-right.tsx";
import IconCircleChevronsLeft from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/circle-chevrons-left.tsx";
const SLIDE_DATA = [
{
color: "bg-green-300",
text: "slide one",
url: asset("/illustration/deno-plush.svg"),
},
{
color: "bg-yellow-300",
text: "slide two",
url: asset("/illustration/lemon-squash.svg"),
},
{
color: "bg-blue-300",
text: "slide three",
url: asset("/illustration/deno-plush.svg"),
},
{
color: "bg-yellow-300",
text: "slide four",
url: asset("/illustration/lemon-squash.svg"),
},
];
type SlideProps = {
class?: string;
key?: number;
data: {
color: string;
text: string;
url: string;
};
};
const Slide = (props: SlideProps) => {
const { key, data } = props;
const { color, text, url } = data;
if (props.class === undefined) props.class = "";
return (
<div
key={key}
class={`${props.class} ${color} h-80 w-full text-center text-black p-5`}
>
{text}
<img src={url} />
</div>
);
};
type CarouselProps = {
showNavigation?: boolean;
interval?: number;
currentSlide?: number;
automatic?: boolean;
class?: string;
};
const Carousel = (props: CarouselProps) => {
const NAVIGATION_COLOR = `hover:text-gray-300 text-white`;
const CHEVRON_STYLE =
`absolute z-30 w-10 h-10 ${NAVIGATION_COLOR} cursor-pointer`;
const SHOW_NAVIGATION = props.showNavigation === false ? false : true;
const SLIDE_INTERVAL = props.interval ? props.interval : 3500;
const currentSlide = useSignal(props.currentSlide ? props.currentSlide : 0);
const automatic = useSignal(props.automatic === false ? false : true);
const slideshowRef = useRef<HTMLDivElement>(null);
const slideClasses = (idx = 0) => {
let outgoingSlide = currentSlide.value - 1;
let incomingSlide = currentSlide.value + 1;
if (outgoingSlide === -1) outgoingSlide = SLIDE_DATA.length - 1;
if (incomingSlide === SLIDE_DATA.length) incomingSlide = 0;
// console.log(outgoingSlide, currentSlide.value, incomingSlide)
const TRANSITION_CLASS = () => {
if (currentSlide.value === idx) return "translate-x-0 z-20";
if (incomingSlide === idx) return "translate-x-full z-10";
if (outgoingSlide === idx) return "-translate-x-full z-10";
return "translate-x-full";
};
return tw`slide absolute top-0 left-0 transition-all ease-in-out duration-700 transform ${TRANSITION_CLASS}`;
};
const nextSlide = () => {
if (SLIDE_DATA.length === currentSlide.value + 1) {
currentSlide.value = 0;
} else {
currentSlide.value++;
}
};
const previousSlide = () => {
if (currentSlide.value === 0) {
currentSlide.value = SLIDE_DATA.length - 1;
} else {
currentSlide.value--;
}
};
const chevronClick = (doCallback = () => {}) => {
if (automatic.value) automatic.value = false;
return doCallback();
};
useEffect(() => {
const interval = setInterval(() => {
if (automatic.value) nextSlide();
}, SLIDE_INTERVAL);
return () => clearInterval(interval);
}, []);
const ArrowKeyNavigation = () => {
const keydownHandler = (event: KeyboardEvent) => {
if (automatic.value) automatic.value = false;
switch (event.code) {
case "ArrowLeft":
event.preventDefault();
previousSlide();
break;
case "ArrowRight":
event.preventDefault();
nextSlide();
break;
default:
break;
}
};
slideshowRef.current?.addEventListener("keydown", keydownHandler);
return () =>
slideshowRef.current?.removeEventListener("keydown", keydownHandler);
};
useEffect(ArrowKeyNavigation, []);
const goToSlide = (slide_index = 0) => {
if (automatic.value) automatic.value = false;
currentSlide.value = slide_index;
};
const DotsNavigation = () => (
<div
class={"slide_nav z-30 w-full absolute bottom-0 flex justify-center cursor-pointer"}
>
{SLIDE_DATA.map((_item, idx) => {
return (
<div
class={`px-1 ${NAVIGATION_COLOR}`}
onClick={() => {
goToSlide(idx);
}}
key={idx}
>
{idx === currentSlide.value ? <>●</> : <>○</>}
</div>
);
})}
</div>
);
return (
<div
ref={slideshowRef}
class={`slideshow relative flex-1 flex-end p-0 overflow-hidden ${
props.class !== undefined ? props.class : ""
}`}
tabIndex={0}
>
<IconCircleChevronsLeft
class={`left-0 ${CHEVRON_STYLE}`}
style="top: calc(50% - 20px)"
onClick={() => chevronClick(previousSlide)}
/>
<IconCircleChevronsRight
class={`right-0 ${CHEVRON_STYLE}`}
style="top: calc(50% - 20px)"
onClick={() => chevronClick(nextSlide)}
/>
{SLIDE_DATA.map((item, idx) => (
<Slide
data={item}
key={idx}
class={slideClasses(idx)}
/>
))}
{SHOW_NAVIGATION &&
<DotsNavigation />}
<Slide
data={SLIDE_DATA[0]}
class="opacity-0 pointer-events-none"
/>
</div>
);
};
export default Carousel;