feat: introducing @classroomio/course-app (#490)

* feat: v1 templates

* update nvm

* working on the displaying the course content

* working on the displaying the course content

* displayed the content

* finished with the logic for rendering the content for all the courses and lessons

* post hog template UI is good to go

* classic and minimalistic template UI is good to go

* some refactoring

* some refactoring

* layout adjustments

* update tsconfig

* trying to get the filter to work well

* this is working now

* cleanup

* cleanup

* UI updates

* course page UI for posthog

* navbar and sidebar for course page in all templates complete

* all templates course page showing lesson content from md file with mdsvex
configured

* wokring on sections

* posthog lesson content done

* all templates fetches the lesson content from the section

* all templates properly typed

* scaffolding the new template

* finished the webflow-saas

* done with the webflow saas UI

* new calcom template

* new calcom template

* masonry layout for testitomonail section layout done

* calcom UI done

* UI all done

* cleanup

* trying to fix dark mode for the course page and home page

* done with this

* done with this

* working on tests

* migrated minimalistic template to svelte 5

* updated the syntax to svelte 5 for webflow

* migrated classic to svelte 5

* migrated posthog to svelte 5

* used svelte 5 syntax in calcom

* cli tool woks locally

* rename to course-sites

* use pnpm in root

* calcom refactoring

* remove unused files

* chore: biggest refactoring in my life

* add basic readme

* refactoring on course page

* restructure how courses are ordered

* proper state propagation

* add cf adapter

* fix pnpm i issue

* fix folder icon size

* use 18.17 version

* update npmrc

* add engine to packagejson

* pnpm install

* add engine to cio-com

* make it 18.x

* remove sequence

* move mdsvex to dependencies

* update lock

* mdsvex please work

* replace fs with import from vite

* add lock

* styling improvements

* fix styling

* sidebar style improvements

* fix: mobile concerns

* fix spacing of course header on desktop

* update header

* top nav fix

* feat: make cal template

* setup another template

* classic basic UI

* fixed the /courses fetching wrong page content

* fix breaking template cause of logo

* adding some updates

* improve styles theming

* finished updates from reviews

* updated the custom styling css

* clean up posthog template

* cli tool done, needs some cleaning though

* moved components to home

* improved the UI of the minimal template

* Add CLI tool and course app site

* prettier lint

* improve CLI

* ignore sveltekit folder

* publish course app

* fix tailwing theme + improve README

* finish up course-app site

* update README.md

---------

Co-authored-by: emmanuel <chifez1@gmail.com>
This commit is contained in:
Rotimi Best
2024-12-14 03:40:47 +01:00
committed by GitHub
co-authored by emmanuel
parent 7e239da7d2
commit 26700dccd0
343 changed files with 17993 additions and 350 deletions
@@ -0,0 +1,48 @@
<script lang="ts">
import { Motion, AnimatePresence, useAnimation } from 'svelte-motion';
import { inview } from 'svelte-inview';
import { cn } from '$lib/utils';
let {
duration = 0.4,
delay = 0,
yOffset = 6,
inViewMargin = '-50px',
blur = '6px',
once = false,
class: className = ''
} = $props();
let id = $state(crypto.randomUUID().slice(0, 8));
let isInView = $state('hidden');
let defaultVariants = {
hidden: { opacity: 0, y: yOffset, filter: `blur(${blur})` },
visible: { opacity: 1, y: 0, filter: `blur(0px)` }
};
</script>
<AnimatePresence let:item list={[{ key: id }]}>
<Motion
initial="hidden"
animate={isInView}
exit="hidden"
variants={defaultVariants}
transition={{
delay: 0.04 + delay,
duration,
ease: 'easeOut'
}}
let:motion
>
<div
use:inview={{ rootMargin: inViewMargin, unobserveOnEnter: once }}
use:motion
on:inview_change={({ detail }) => {
isInView = detail.inView ? 'visible' : 'hidden';
}}
class={cn(className)}
>
<slot>Default</slot>
</div>
</Motion>
</AnimatePresence>
@@ -0,0 +1 @@
export { default as BlurFade } from './blurfade.svelte';
@@ -0,0 +1,36 @@
<script lang="ts">
import { cn } from "$lib/utils";
import { Motion } from "svelte-motion";
export let variant: {
hidden: { filter: string; opacity: number };
visible: { filter: string; opacity: number };
} = {
hidden: { filter: "blur(10px)", opacity: 0 },
visible: { filter: "blur(0px)", opacity: 1 },
};
export let duration: number = 1;
let className = "font-display text-4xl font-bold tracking-[-0.02em] drop-shadow-sm md:text-7xl md:leading-[5rem]";
export { className as class };
let defaultVariants = {
hidden: { filter: "blur(10px)", opacity: 0 },
visible: { filter: "blur(0px)", opacity: 1 },
};
let combinedVariants = variant || defaultVariants;
</script>
<Motion
initial="hidden"
animate="visible"
transition={{ duration }}
variants={combinedVariants}
let:motion
>
<h1
class={cn(className)}
use:motion
>
<slot />
</h1>
</Motion>
@@ -0,0 +1 @@
export { default as BlurIn } from './blurin.svelte';
@@ -0,0 +1,21 @@
<script lang="ts">
import { cn } from '$lib/utils';
let { children, className } = $props();
// let cardContent = {
// title: 'Svelte is Vibe',
// description: 'Svelte is a fun way to build web applications.'
// };
</script>
<div class={cn('p-4 text-left', className)}>
<!-- <h3 class="mb-1 text-lg font-bold text-gray-900 dark:text-gray-100">
{cardContent.title}
</h3>
<p class="text-gray-700 dark:text-gray-300">
{cardContent.description}
</p> -->
{@render children()}
</div>
@@ -0,0 +1,30 @@
<script lang="ts">
import { cn } from '$lib/utils';
import Ellipses from './ellipses.svelte';
type Props = {
className?: string;
};
let { className }: Props = $props();
</script>
<div
class={cn(
'relative mx-auto w-full border border-dashed border-zinc-300 px-8 shadow-sm sm:px-6 md:px-8 dark:border-zinc-800 dark:bg-[#09090B]',
className
)}
>
<div
class="absolute left-0 top-4 -z-0 h-px w-full bg-zinc-400 sm:top-6 md:top-8 dark:bg-zinc-700"
></div>
<div
class="absolute bottom-4 left-0 z-0 h-px w-full bg-zinc-400 sm:bottom-6 md:bottom-8 dark:bg-zinc-700"
></div>
<div class="relative w-full border-x border-zinc-400 dark:border-zinc-700">
<Ellipses />
<div class="relative z-20 mx-auto py-8">
<slot>Default</slot>
</div>
</div>
</div>
@@ -0,0 +1,13 @@
<script>
let sharedClass =
'rounded-full outline outline-8 dark:outline-gray-950 sm:my-6 md:my-8 size-1 my-4 outline-gray-50 bg-green-400';
</script>
<div class="absolute z-0 grid h-full w-full items-center gap-8 lg:grid-cols-2">
<section class="absolute z-0 grid h-full w-full grid-cols-2 place-content-between">
<div class="{sharedClass} -mx-[2.5px]"></div>
<div class="{sharedClass} -mx-[2px] place-self-end"></div>
<div class="{sharedClass} -mx-[2.5px]"></div>
<div class="{sharedClass} -mx-[2px] place-self-end"></div>
</section>
</div>
@@ -0,0 +1,2 @@
export { default as CardContainer } from './container.svelte';
export { default as CardBody } from './body.svelte';
@@ -0,0 +1,42 @@
<script lang="ts">
import { cn } from '$lib/utils';
import { AnimatePresence, Motion } from 'svelte-motion';
let className: any = 'Gradual Spacing';
export { className as class };
export let words = 'Gradual Spacing';
export let duration = 0.5;
export let delayMultiple = 0.04;
export let framerProps = {
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0 }
};
let wordsspilit = words.split('');
</script>
<div class="flex justify-center space-x-1">
<AnimatePresence let:item list={[{ key: wordsspilit }]}>
{#each wordsspilit as char, i}
<Motion
initial="hidden"
animate="visible"
exit="hidden"
variants={framerProps}
transition={{
duration: duration,
delay: i * delayMultiple
}}
let:motion
>
<span use:motion class={cn('drop-shadow-sm', className)}>
{#if char === ' '}
<span>&nbsp;</span>
{:else}
{char}
{/if}
</span>
</Motion>
{/each}
</AnimatePresence>
</div>
@@ -0,0 +1 @@
export { default as GradualSpacing } from './gradual-spacing.svelte';
@@ -0,0 +1 @@
export { default as MagicCard } from './magic-card.svelte';
@@ -0,0 +1,72 @@
<script lang="ts">
import { Motion, useMotionValue, useMotionTemplate } from 'svelte-motion';
import { onMount } from 'svelte';
import { cn } from '$lib/utils';
export let gradientSize: number = 200;
export let gradientColor: string = '#262626';
export let gradientOpacity: number = 0.8;
let className: string = '';
export { className as class };
let gradSize = useMotionValue(gradientSize);
let gradColor = useMotionValue(gradientColor);
let mouseX = useMotionValue(-gradientSize);
let mouseY = useMotionValue(-gradientSize);
function handleMouseMove(e: MouseEvent) {
const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
mouseX.set(e.clientX - rect.left);
mouseY.set(e.clientY - rect.top);
}
function handleMouseLeave() {
mouseX.set(-gradientSize);
mouseY.set(-gradientSize);
}
onMount(() => {
mouseX.set(-gradientSize);
mouseY.set(-gradientSize);
});
let bg = useMotionTemplate`radial-gradient(${gradSize}px circle at ${mouseX}px ${mouseY}px, ${gradColor}, transparent 100%)`;
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- I have added py-4 in below code, you can customize the component as per needs -->
<div
on:mousemove={handleMouseMove}
on:mouseleave={handleMouseLeave}
class={cn(
'group relative flex size-full justify-center overflow-hidden rounded-xl border bg-neutral-100 py-4 text-black dark:bg-neutral-900 dark:text-white',
className
)}
>
<div class="relative z-10">
<!-- Default -->
<slot>
<div class="flex h-full items-center justify-center text-center">
<p class="text-2xl">Magic Card</p>
</div>
</slot>
</div>
<Motion
style={{
background: bg,
opacity: gradientOpacity
}}
let:motion
>
<div
use:motion
class="pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
/>
</Motion>
</div>
<style>
.size-full {
width: 100%;
height: 100%;
}
</style>
@@ -0,0 +1 @@
export { default as Meteors } from './meteors.svelte';
@@ -0,0 +1,36 @@
<script lang="ts">
import { cn } from '$lib/utils';
import { onMount } from 'svelte';
export let number = 10;
let meteorStyles: any = [];
let changeMeteors = (num: number) => {
meteorStyles = [];
const styles = [...new Array(num)].map(() => ({
top: -20,
left: Math.floor(Math.random() * 700) + 'px',
animationDelay: Math.random() * 1 + 0.2 + 's',
animationDuration: Math.floor(Math.random() * 8 + 2.9) + 's'
}));
meteorStyles = styles;
};
onMount(() => {
changeMeteors(number);
});
// $: changeMeteors(number);
</script>
{#each meteorStyles as style, idx}
<span
id="meteor-{idx + 1}"
class={cn(
'animate-meteor pointer-events-none absolute left-1/2 top-1/2 size-[2.4px] rotate-[215deg] rounded-full bg-slate-500 shadow-[0_0_0_1px_#ffffff10]'
)}
style="top: {style.top}px; left: {style.left}; animation-delay: {style.animationDelay}; animation-duration: {style.animationDuration};"
>
<!-- Meteor Tail -->
<div
class="pointer-events-none absolute top-1/2 -z-10 h-px w-[50px] -translate-y-1/2 bg-gradient-to-r from-slate-500 via-blue-600/30 to-transparent"
/>
</span>
{/each}
@@ -0,0 +1 @@
export { default as ShimmerButton } from './shimmerbutton.svelte';
@@ -0,0 +1,62 @@
<script lang="ts">
import { cn } from '$lib/utils';
export let shimmerColor = '#ffffff';
export let shimmerSize = '0.05em';
export let shimmerDuration = '3s';
export let borderRadius = '100px';
export let background = 'rgba(0, 0, 0, 1)';
export let onClick: () => void;
let className: any = '';
export { className as class };
</script>
<button
style:--spread="90deg"
style:--shimmer-color={shimmerColor}
style:--radius={borderRadius}
style:--speed={shimmerDuration}
style:--cut={shimmerSize}
style:--bg={background}
class={cn(
'group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden whitespace-nowrap border border-white/10 px-6 py-3 text-white [background:var(--bg)] [border-radius:var(--radius)] dark:text-white',
'transform-gpu transition-transform duration-300 ease-in-out active:translate-y-[1px]',
className
)}
on:click={onClick}
>
<div class={cn('-z-30 blur-[2px]', 'absolute inset-0 overflow-visible [container-type:size]')}>
<div
class="animate-magicslide absolute inset-0 h-[100cqh] [aspect-ratio:1] [border-radius:0] [mask:none]"
>
<!-- spark before -->
<div
class="animate-spin-around absolute inset-[-100%] w-auto rotate-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))] [translate:0_0]"
/>
</div>
</div>
<slot>Shimmer Button</slot>
<!-- Highlight -->
<div
class={cn(
'insert-0 absolute h-full w-full',
'rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#ffffff1f]',
// transition
'transform-gpu transition-all duration-300 ease-in-out',
// on hover
'group-hover:shadow-[inset_0_-6px_10px_#ffffff3f]',
// on click
'group-active:shadow-[inset_0_-10px_10px_#ffffff3f]'
)}
></div>
<!-- backdrop -->
<div
class={cn(
'absolute -z-20 [background:var(--bg)] [border-radius:var(--radius)] [inset:var(--cut)]'
)}
></div>
</button>
@@ -0,0 +1 @@
export { default as ShinnyText } from './shinny-text.svelte';
@@ -0,0 +1,24 @@
<script lang="ts">
import { cn } from '$lib/utils';
export let shimmerWidth = 100;
let className: any = '';
export { className as class };
</script>
<p
style:--shimmer-width="{shimmerWidth}px"
class={cn(
'mx-auto max-w-md text-neutral-600/50 dark:text-neutral-400/50 ',
// Shimmer effect
'animate-shimmer bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shimmer-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]',
// Shimmer gradient
'bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80',
className
)}
>
<slot>Shimmer Animation</slot>
</p>
@@ -0,0 +1,9 @@
<script lang="ts">
import { cn } from '$lib/utils';
let { children, className } = $props();
</script>
<div class={cn('text-left', className)}>
{@render children()}
</div>
@@ -0,0 +1,21 @@
<script lang="ts">
import { cn } from '$lib/utils';
let className: any = '';
export { className as class };
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
width={24}
height={24}
stroke-width="1"
stroke-linecap="round"
stroke="currentColor"
class={cn('absolute text-black dark:text-white', className)}
{...$$restProps}
>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
</svg>
@@ -0,0 +1,2 @@
export { default as SquareCardBody } from './body.svelte';
export { default as SquareCardIcon } from './icon.svelte';