Skip to content

Commit

Permalink
Merge pull request #17 from FiligranHQ/issue/add-quizz-component
Browse files Browse the repository at this point in the history
Issue/add quizz component
  • Loading branch information
jpkha authored Oct 11, 2024
2 parents 47643bb + 4e89a14 commit 9fc7391
Show file tree
Hide file tree
Showing 29 changed files with 2,255 additions and 212 deletions.
1 change: 0 additions & 1 deletion projects/filigran-website/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
@tailwind components;
@tailwind utilities;


@layer base {
* {
@apply border-border;
Expand Down
2 changes: 1 addition & 1 deletion projects/filigran-website/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
"components": "@/components",
"utils": "@/utils"
}
}
}
8 changes: 8 additions & 0 deletions projects/filigran-website/components/mdx-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import {ExampleCombobox} from '@/components/example/example-combobox'
import {ExampleToast} from '@/components/example/example-toast'
import {ExampleSheet} from '@/components/example/example-sheet'
import {ExampleTagInput} from '@/components/example/example-tag-input'
import {
Quizz,
QuizzExplanation,
QuizzQuestionChoice,
} from '@/components/ui/quizz'

const options = {
mdxOptions: {
Expand All @@ -33,6 +38,9 @@ const components = {
ExampleToast: ExampleToast,
ExampleSheet: ExampleSheet,
ExampleTagInput: ExampleTagInput,
Quizz: Quizz,
QuizzQuestionChoice: QuizzQuestionChoice,
QuizzExplanation: QuizzExplanation,
}

export async function CustomMDX(props: any) {
Expand Down
137 changes: 137 additions & 0 deletions projects/filigran-website/components/primitives/core-slot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as React from "react";

import { composeRefs } from "./use-compose-refs";

interface SlotProps extends React.HTMLAttributes<HTMLElement> {
children?: React.ReactNode;
}

interface SlotCloneProps {
children: React.ReactNode;
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SLOTTABLE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

const Slottable = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};

type AnyProps = Record<string, any>;

function isSlottable(child: React.ReactNode): child is React.ReactElement {
return React.isValidElement(child) && child.type === Slottable;
}

//
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SLOT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

const Slot = React.forwardRef<HTMLElement, SlotProps>((props, forwardedRef) => {
const { children, ...slotProps } = props;
const childrenArray = React.Children.toArray(children);
const slottable = childrenArray.find(isSlottable);

if (slottable) {
// the new element to render is the one passed as a child of `Slottable`
const newElement = slottable.props.children as React.ReactNode;

const newChildren = childrenArray.map((child) => {
if (child === slottable) {
// because the new element will be the one rendered, we are only interested
// in grabbing its children (`newElement.props.children`)
if (React.Children.count(newElement) > 1) {
return React.Children.only(null);
}
return React.isValidElement(newElement)
? (newElement.props.children as React.ReactNode)
: null;
}
return child;
});

return (
<SlotClone {...slotProps} ref={forwardedRef}>
{React.isValidElement(newElement)
? React.cloneElement(newElement, undefined, newChildren)
: null}
</SlotClone>
);
}

return (
<SlotClone {...slotProps} ref={forwardedRef}>
{children}
</SlotClone>
);
});

Slot.displayName = "Slot";

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SLOT CLONE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

const SlotClone = React.forwardRef<any, SlotCloneProps>((props, forwardedRef) => {
const { children, ...slotProps } = props;

if (React.isValidElement(children)) {
return React.cloneElement(children, {
...mergeProps(slotProps, children.props),
// @ts-expect-error: No overload matches this call.
ref: forwardedRef
? composeRefs(forwardedRef, (children as any).ref)
: (children as any).ref,
});
}

return React.Children.count(children) > 1 ? React.Children.only(null) : null;
});

SlotClone.displayName = "SlotClone";

//
//
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MERGE PROPS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

function mergeProps(slotProps: AnyProps, childProps: AnyProps) {
// all child props should override
const overrideProps = { ...childProps };

for (const propName in childProps) {
const slotPropValue = slotProps[propName];
const childPropValue = childProps[propName];

const isHandler = /^on[A-Z]/.test(propName);
if (isHandler) {
// if the handler exists on both, we compose them
if (slotPropValue && childPropValue) {
overrideProps[propName] = (...args: unknown[]) => {
childPropValue(...args);
slotPropValue(...args);
};
}
// but if it exists only on the slot, we use only this one
else if (slotPropValue) {
overrideProps[propName] = slotPropValue;
}
}
// if it's `style`, we merge them
else if (propName === "style") {
overrideProps[propName] = { ...slotPropValue, ...childPropValue };
} else if (propName === "className") {
overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
}
}

return { ...slotProps, ...overrideProps };
}

const Root = Slot;

export { Slot, Slottable, Root };
export type { SlotProps };
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from "react";

type PossibleRef<T> = React.Ref<T> | undefined;

/**
* Set a given ref to a given value
* This utility takes care of different types of refs: callback refs and RefObject(s)
*/
function setRef<T>(ref: PossibleRef<T>, value: T) {
if (typeof ref === "function") {
ref(value);
} else if (ref !== null && ref !== undefined) {
(ref as React.MutableRefObject<T>).current = value;
}
}

/**
* A utility to compose multiple refs together
* Accepts callback refs and RefObject(s)
*/
function composeRefs<T>(...refs: PossibleRef<T>[]) {
return (node: T) => refs.forEach((ref) => setRef(ref, node));
}

/**
* A custom hook that composes multiple refs
* Accepts callback refs and RefObject(s)
*/
function useComposedRefs<T>(...refs: PossibleRef<T>[]) {
// eslint-disable-next-line react-hooks/exhaustive-deps
return React.useCallback(composeRefs(...refs), refs);
}

export { composeRefs, useComposedRefs };
175 changes: 175 additions & 0 deletions projects/filigran-website/components/ui/_shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 💪 CORE 💪 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

export const STYLES = {
CONTENT_OVERFLOW_POPOVER:
"z-50 overflow-hidden rounded-md border bg-popover text-popover-foreground",
ACCENT: "focus:bg-accent focus:text-accent-foreground",
ACCENT_FOCUS: "focus:bg-accent focus:text-accent-foreground",
ACCENT_STATE_OPEN: "focus:bg-accent data-[state=open]:bg-accent",
// BORDER_INPUT: "border border-input",
BORDER_INPUT: "border",
CURSOR_DEFAULT: "cursor-default",
DISABLED_NOT_ALLOWED: "disabled:cursor-not-allowed disabled:opacity-50",
DISABLED_EVENTS_NONE: "disabled:pointer-events-none disabled:opacity-50",
DISABLED_EVENTS_NONE_DATA:
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
OFFSET_BG: "ring-offset-background",
RING_FOCUS: "focus:outline-none focus:ring-1 focus:ring-ring focus:ring-offset-1",
RING_FOCUS_VISIBLE:
"focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1",
FLEX_CENTER: "flex items-center",
FLEX_CENTER_JUSTIFIED: "flex items-center justify-center",
FULL_CENTER_INLINE: "inline-flex items-center justify-center",
FLEX_BETWEEN: "flex items-center justify-between",
FLEX_COL: "flex flex-col",
FLEX_WRAP: "flex flex-wrap items-center",
GRID_START: "grid w-full grid-cols-1 items-start",
GROUP_RELATIVE: "group relative",
SIZE_FULL: "h-full w-full",
TEXT_MUTED_PLACEHOLDER: "placeholder:text-muted-foreground",
MARQUEE: "pointer-events-none absolute from-white dark:from-background",
// DATA STATES
DATA_STATE_CHECKED:
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
DATA_STATE_TABS:
"data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
// DEMOS
DEMO_CARD:
"relative flex min-h-[300px] h-full w-full items-center justify-center rounded-lg border bg-background p-20 md:shadow-xl overflow-hidden",
DEMO_TITLE:
"z-10 whitespace-pre-wrap text-center text-5xl font-medium tracking-tighter text-black dark:text-white",
} as const;

//

//
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 💫 MOTION 💫 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

export const MOTION = {
ANIMATE_IN: "data-[state=open]:animate-in",
ANIMATE_OUT: "data-[state=closed]:animate-out",
FADE_IN_OUT: "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
ZOOM_IN_OUT: "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
SLIDE_IN:
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
DIALOG_SLIDE_IN_OUT:
"data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
TODO_STATE_TOOLTIP:
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
// TAILWIND CONFIG
ANIMATE_ACCORDION:
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
ANIMATE_BG_RETRO: "animate-grid",
ANIMATE_BG_RETRO_LIGHT:
"[background-image:linear-gradient(to_right,rgba(0,0,0,0.3)_1px,transparent_0),linear-gradient(to_bottom,rgba(0,0,0,0.3)_1px,transparent_0)]",
ANIMATE_BG_RETRO_DARK:
"dark:[background-image:linear-gradient(to_right,rgba(255,255,255,0.2)_1px,transparent_0),linear-gradient(to_bottom,rgba(255,255,255,0.2)_1px,transparent_0)]",
ANIMATE_BG_RIPPLE: "animate-ripple",
ANIMATE_BEAM_BORDER:
"absolute inset-[0] rounded-[inherit] [border:calc(var(--border-width)*1px)_solid_transparent] ![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(transparent,transparent),linear-gradient(white,white)] after:animate-beam-border after:absolute after:aspect-square after:w-[calc(var(--size)*1px)] after:[animation-delay:var(--delay)] after:[background:linear-gradient(to_left,var(--color-from),var(--color-to),transparent)] after:[offset-anchor:calc(var(--anchor)*1%)_50%] after:[offset-path:rect(0_auto_auto_0_round_calc(var(--size)*1px))]",
ANIMATE_LOGO_SECTION: "animate-logo-section flex shrink-0 flex-row justify-around",
ANIMATE_METEOR_EFFECT: "animate-meteor-effect",
ANIMATE_RADAR_SPIN: "animate-radar-spin",
ANIMATE_FADE_UP: "animate-fade-up opacity-0",
ANIMATE_SCROLL_FADE_OUT:
"animate-fade-out-down [animation-range:0px_300px] [animation-timeline:scroll()] supports-no-scroll-driven-animations:animate-none",
ANIMATE_SCROLL_BIGGER:
"animate-make-it-bigger [animation-range:0%_60%] [animation-timeline:--quote] [view-timeline-name:--quote] supports-no-scroll-driven-animations:animate-none",
ANIMATE_TEXT_SHIMMER:
"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]",
ANIMATE_TEXT_GRADIENT:
"inline animate-gradient bg-gradient-to-r bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent",
ANIMATE_TEXT_GRADIENT_BTN:
"animate-gradient absolute inset-0 block h-full w-full bg-gradient-to-r bg-[length:var(--bg-size)_100%] p-[1px] [border-radius:inherit] ![mask-composite:subtract] [mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)]",
// ------------- 👇 [ANIMATIONS] 👇 ------------- //
ANIMATE_BLINK: "animate-blink",
ANIMATE_BLURRED_FADE_IN: "animate-blurred-fade-in",
ANIMATE_BOUNCE_VERTICAL: "animate-bounce-vertical",
ANIMATE_BOUNCE_HORIZONTAL: "animate-bounce-horizontal",
ANIMATE_CONTRACT_HORIZONTALLY: "animate-contract-horizontally",
ANIMATE_CONTRACT_VERTICALLY: "animate-contract-vertically",
ANIMATE_EXPAND_HORIZONTALLY: "animate-expand-horizontally",
ANIMATE_EXPAND_VERTICALLY: "animate-expand-vertically",
ANIMATE_FADE_OUT_UP: "animate-fade-out-up",
ANIMATE_FADE_OUT_DOWN_V2: "animate-fade-out-down-v2", // TODO: V2
ANIMATE_FADE_OUT_LEFT: "animate-fade-out-left",
ANIMATE_FADE_OUT_RIGHT: "animate-fade-out-right",
ANIMATE_FLASH_V0: "animate-flashV0", // TODO
ANIMATE_FLASH: "animate-flash",
ANIMATE_FLIP_HORIZONTAL: "animate-flip-horizontal",
ANIMATE_FLIP_VERTICAL: "animate-flip-vertical",
ANIMATE_FLIP_X: "animate-flip-x",
ANIMATE_FLIP_Y: "animate-flip-y",
ANIMATE_FLIP_IN_Y: "animate-flip-in-y",
ANIMATE_FLIP_IN_X: "animate-flip-in-x",
ANIMATE_FLIP_OUT_Y: "animate-flip-out-y",
ANIMATE_FLIP_OUT_X: "animate-flip-out-x",
ANIMATE_FLOAT: "animate-float",
ANIMATE_HANG: "animate-hang",
ANIMATE_HORIZONTAL_VIBRATION: "animate-horizontal-vibration",
ANIMATE_JIGGLE_V0: "animate-jiggle-v0", // TODO
ANIMATE_JIGGLE: "animate-jiggle",
ANIMATE_JUMP: "animate-jump",
ANIMATE_POP: "animate-pop",
ANIMATE_RUBBER_BAND_V0: "animate-rubber-band-v0", // TODO
ANIMATE_RUBBER_BAND: "animate-rubber-band",
ANIMATE_RISE: "animate-rise",
ANIMATE_ROTATE_90: "animate-rotate-90",
ANIMATE_ROTATE_180: "animate-rotate-180",
ANIMATE_ROTATE_360: "animate-rotate-360",
ANIMATE_ROTATE_IN: "animate-rotate-in",
ANIMATE_ROLL_IN: "animate-roll-in",
ANIMATE_ROLL_OUT: "animate-roll-out",
ANIMATE_ROTATIONAL_WAVE: "animate-rotational-wave",
ANIMATE_ROTATE_OUT: "animate-rotate-out",
ANIMATE_SCALE: "animate-scale",
ANIMATE_SINK: "animate-sink",
ANIMATE_SKEW: "animate-skew",
ANIMATE_SWAY: "animate-sway",
ANIMATE_SHAKE_V0: "animate-shakeV0", // TODO
ANIMATE_SHAKE: "animate-shake",
ANIMATE_SLIDE_IN_TOP: "animate-slide-in-top",
ANIMATE_SLIDE_IN_BOTTOM: "animate-slide-in-bottom",
ANIMATE_SLIDE_IN_LEFT: "animate-slide-in-left",
ANIMATE_SLIDE_IN_RIGHT: "animate-slide-in-right",
ANIMATE_SLIDE_OUT_TOP: "animate-slide-out-top",
ANIMATE_SLIDE_OUT_BOTTOM: "animate-slide-out-bottom",
ANIMATE_SLIDE_OUT_LEFT: "animate-slide-out-left",
ANIMATE_SLIDE_OUT_RIGHT: "animate-slide-out-right",
ANIMATE_SLIDE_ROTATE_IN: "animate-slide-rotate-in",
ANIMATE_SLIDE_ROTATE_OUT: "animate-slide-rotate-out",
ANIMATE_SLIDE_UP: "animate-slide-up",
ANIMATE_SLIDE_DOWN: "animate-slide-down",
ANIMATE_SLIDE_LEFT: "animate-slide-left",
ANIMATE_SLIDE_RIGHT: "animate-slide-right",
ANIMATE_SLIDE_DOWN_AND_FADE: "animate-slide-down-and-fade",
ANIMATE_SLIDE_LEFT_AND_FADE: "animate-slide-left-and-fade",
ANIMATE_SLIDE_UP_AND_FADE: "animate-slide-up-and-fade",
ANIMATE_SLIDE_RIGHT_AND_FADE: "animate-slide-right-and-fade",
ANIMATE_SPIN_CLOCKWISE: "animate-spin-clockwise",
ANIMATE_SPIN_COUNTER_CLOCKWISE: "animate-spin-counter-clockwise",
ANIMATE_SWING_V0: "animate-swingV0", // TODO
ANIMATE_SWING: "animate-swing",
ANIMATE_TADA: "animate-tada",
ANIMATE_VIBRATE: "animate-vibrate",
ANIMATE_WOBBLE: "animate-wobble",
ANIMATE_ZOOM_IN: "animate-zoom-in",
ANIMATE_ZOOM_OUT: "animate-zoom-out",
ANIMATE_TILT_HORIZONTAL: "animate-tilt-horizontal",
ANIMATE_SQUEEZE: "animate-squeeze",
ANIMATE_SLIDE_UP_FADE: "animate-slide-up-fade",
ANIMATE_BOUNCE_FADE_IN: "animate-bounce-fade-in",
ANIMATE_PULSE_FADE_IN: "animate-pulse-fade-in",
ANIMATE_SWING_DROP_IN: "animate-swing-drop-in",
ANIMATE_BOUNCE_CUSTOM: "animate-bounceCustom",
ANIMATE_PULSE_CUSTOM: "animate-pulseCustom",
ANIMATE_BOUNCE: "animate-bounce", // by default with Tailwind
ANIMATE_PULSE: "animate-pulse", // by default with Tailwind
ANIMATE_SPIN: "animate-spin", // by default with Tailwind
ANIMATE_PING: "animate-ping", // by default with Tailwind
// ------------- 👆 [ANIMATIONS] 👆 ------------- //
} as const;
Loading

0 comments on commit 9fc7391

Please sign in to comment.