Migrate the root layout from Vercel. (#2)
parent
ccc8423e0f
commit
a1dd46561d
|
@ -1,24 +1,32 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
mixFiles=$(
|
STAGED=$(
|
||||||
git --no-pager diff --name-status --no-color --cached | \
|
git --no-pager diff --name-only --no-color --cached --diff-filter=d |
|
||||||
awk '$1 != "D" && $2 ~ /\.exs?$/ {print $NF}'
|
# Remove quotations used to surrounding filenames with special characters.
|
||||||
|
sed -e "s/^\"//" -e "s/\"$//g"
|
||||||
)
|
)
|
||||||
|
|
||||||
for path in $mixFiles
|
MIX_TARGETS=()
|
||||||
|
WEB_TARGETS=()
|
||||||
|
while IFS= read -r FILENAME
|
||||||
do
|
do
|
||||||
mix format "$path"
|
if [[ "$FILENAME" =~ .*\.exs? ]]; then
|
||||||
git add "$path"
|
MIX_TARGETS+=("${FILENAME}")
|
||||||
done
|
elif
|
||||||
|
[[ "$FILENAME" =~ assets/.*\.jsx? ]] ||
|
||||||
|
[[ "$FILENAME" =~ assets/.*\.tsx? ]]; then
|
||||||
|
WEB_TARGETS+=("${FILENAME#"assets/"}")
|
||||||
|
fi
|
||||||
|
done <<< "$STAGED"
|
||||||
|
|
||||||
webFiles=$(
|
if (( ${#MIX_TARGETS[@]} )); then
|
||||||
git --no-pager diff --name-status --no-color --cached | \
|
mix format "${MIX_TARGETS[@]}"
|
||||||
awk '$1 != "D" && $2 ~ /\.jsx?$|\.tsx?$/ {print $NF}'
|
git add "${MIX_TARGETS[@]}"
|
||||||
)
|
fi
|
||||||
|
|
||||||
for path in $webFiles
|
if (( ${#WEB_TARGETS[@]} )); then
|
||||||
do
|
cd assets
|
||||||
prettier --write "$path"
|
npx prettier --write "${WEB_TARGETS[@]}"
|
||||||
git add "$path"
|
git add "${WEB_TARGETS[@]}"
|
||||||
done
|
fi
|
||||||
|
|
|
@ -14,16 +14,19 @@ html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
font-feature-settings: 'case' 1, 'rlig' 1, 'calt' 0;
|
font-feature-settings:
|
||||||
|
"case" 1,
|
||||||
|
"rlig" 1,
|
||||||
|
"calt" 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Helvetica Neue',
|
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Helvetica Neue",
|
||||||
'Helvetica', sans-serif;
|
"Helvetica", sans-serif;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
@apply text-white bg-white antialiased;
|
@apply bg-white text-white antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -22,13 +22,17 @@ import { Socket } from "phoenix"
|
||||||
import { LiveSocket } from "phoenix_live_view"
|
import { LiveSocket } from "phoenix_live_view"
|
||||||
import topbar from "../vendor/topbar"
|
import topbar from "../vendor/topbar"
|
||||||
|
|
||||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
let csrfToken = document
|
||||||
let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken } })
|
.querySelector("meta[name='csrf-token']")
|
||||||
|
.getAttribute("content")
|
||||||
|
let liveSocket = new LiveSocket("/live", Socket, {
|
||||||
|
params: { _csrf_token: csrfToken },
|
||||||
|
})
|
||||||
|
|
||||||
// Show progress bar on live navigation and form submits
|
// Show progress bar on live navigation and form submits
|
||||||
topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" })
|
topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" })
|
||||||
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
|
window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300))
|
||||||
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
|
window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide())
|
||||||
|
|
||||||
// connect if there are any LiveViews on the page
|
// connect if there are any LiveViews on the page
|
||||||
liveSocket.connect()
|
liveSocket.connect()
|
||||||
|
@ -38,4 +42,3 @@ liveSocket.connect()
|
||||||
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
||||||
// >> liveSocket.disableLatencySim()
|
// >> liveSocket.disableLatencySim()
|
||||||
window.liveSocket = liveSocket
|
window.liveSocket = liveSocket
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,13 @@
|
||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
import { RouterProvider } from "react-router-dom"
|
||||||
import { Footer } from "./components/Footer";
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
import { RootLayout } from "./components/RootLayout"
|
||||||
{
|
import { router } from "./router"
|
||||||
path: "/",
|
|
||||||
element: <Footer />, // Placeholder.
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/nested",
|
|
||||||
element: <Footer />, // Placeholder.
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<RootLayout>
|
||||||
<main className="w-full flex-auto">
|
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</main>
|
</RootLayout>
|
||||||
<Footer />
|
)
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
import clsx from "clsx";
|
import clsx from "clsx"
|
||||||
|
|
||||||
type ContainerProps<T extends React.ElementType> = {
|
type ContainerProps<T extends React.ElementType> = {
|
||||||
as?: T;
|
as?: T
|
||||||
className?: string;
|
className?: string
|
||||||
children: React.ReactNode;
|
children: React.ReactNode
|
||||||
};
|
}
|
||||||
|
|
||||||
export function Container<T extends React.ElementType = "div">({
|
export function Container<T extends React.ElementType = "div">({
|
||||||
as,
|
as,
|
||||||
|
@ -13,11 +13,11 @@ export function Container<T extends React.ElementType = "div">({
|
||||||
children,
|
children,
|
||||||
}: Omit<React.ComponentPropsWithoutRef<T>, keyof ContainerProps<T>> &
|
}: Omit<React.ComponentPropsWithoutRef<T>, keyof ContainerProps<T>> &
|
||||||
ContainerProps<T>) {
|
ContainerProps<T>) {
|
||||||
let Component = as ?? "div";
|
let Component = as ?? "div"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component className={clsx("mx-auto max-w-7xl px-6 lg:px-8", className)}>
|
<Component className={clsx("mx-auto max-w-7xl px-6 lg:px-8", className)}>
|
||||||
<div className="mx-auto max-w-2xl lg:max-w-none">{children}</div>
|
<div className="mx-auto max-w-2xl lg:max-w-none">{children}</div>
|
||||||
</Component>
|
</Component>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
|
|
||||||
import { Container } from "./Container";
|
import { Container } from "./Container"
|
||||||
import { Logo } from "./Logo";
|
import { Logo } from "./Logo"
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,7 @@ const navigation = [
|
||||||
{ title: "Contact Us", href: "/contact/" },
|
{ title: "Contact Us", href: "/contact/" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
||||||
function Navigation() {
|
function Navigation() {
|
||||||
return (
|
return (
|
||||||
|
@ -42,7 +42,7 @@ function Navigation() {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
|
@ -60,5 +60,5 @@ export function Footer() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { motion } from "framer-motion"
|
||||||
|
|
||||||
|
function Block({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
...props
|
||||||
|
}: Omit<React.ComponentPropsWithoutRef<typeof motion.path>, "x" | "y"> & {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<motion.path
|
||||||
|
transform={`translate(${-32 * y + 96 * x} ${160 * y})`}
|
||||||
|
d="M45.119 4.5a11.5 11.5 0 0 0-11.277 9.245l-25.6 128C6.82 148.861 12.262 155.5 19.52 155.5h63.366a11.5 11.5 0 0 0 11.277-9.245l25.6-128c1.423-7.116-4.02-13.755-11.277-13.755H45.119Z"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GridPattern({
|
||||||
|
yOffset = 0,
|
||||||
|
interactive = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentPropsWithoutRef<"svg"> & {
|
||||||
|
yOffset?: number
|
||||||
|
interactive?: boolean
|
||||||
|
}) {
|
||||||
|
let id = React.useId()
|
||||||
|
let ref = React.useRef<React.ElementRef<"svg">>(null)
|
||||||
|
let currentBlock = React.useRef<[x: number, y: number]>()
|
||||||
|
let counter = React.useRef(0)
|
||||||
|
let [hoveredBlocks, setHoveredBlocks] = React.useState<
|
||||||
|
Array<[x: number, y: number, key: number]>
|
||||||
|
>([])
|
||||||
|
let staticBlocks = [
|
||||||
|
[1, 1],
|
||||||
|
[2, 2],
|
||||||
|
[4, 3],
|
||||||
|
[6, 2],
|
||||||
|
[7, 4],
|
||||||
|
[5, 5],
|
||||||
|
]
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!interactive) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseMove(event: MouseEvent) {
|
||||||
|
if (!ref.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let rect = ref.current.getBoundingClientRect()
|
||||||
|
let x = event.clientX - rect.left
|
||||||
|
let y = event.clientY - rect.top
|
||||||
|
if (x < 0 || y < 0 || x > rect.width || y > rect.height) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x - rect.width / 2 - 32
|
||||||
|
y = y - yOffset
|
||||||
|
x += Math.tan(32 / 160) * y
|
||||||
|
x = Math.floor(x / 96)
|
||||||
|
y = Math.floor(y / 160)
|
||||||
|
|
||||||
|
if (currentBlock.current?.[0] === x && currentBlock.current?.[1] === y) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBlock.current = [x, y]
|
||||||
|
|
||||||
|
setHoveredBlocks((blocks) => {
|
||||||
|
let key = counter.current++
|
||||||
|
let block = [x, y, key] as (typeof hoveredBlocks)[number]
|
||||||
|
return [...blocks, block].filter(
|
||||||
|
(block) => !(block[0] === x && block[1] === y && block[2] !== key)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", onMouseMove)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("mousemove", onMouseMove)
|
||||||
|
}
|
||||||
|
}, [yOffset, interactive])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg ref={ref} aria-hidden="true" {...props}>
|
||||||
|
<rect width="100%" height="100%" fill={`url(#${id})`} strokeWidth="0" />
|
||||||
|
<svg x="50%" y={yOffset} strokeWidth="0" className="overflow-visible">
|
||||||
|
{staticBlocks.map((block) => (
|
||||||
|
<Block key={`${block}`} x={block[0]} y={block[1]} />
|
||||||
|
))}
|
||||||
|
{hoveredBlocks.map((block) => (
|
||||||
|
<Block
|
||||||
|
key={block[2]}
|
||||||
|
x={block[0]}
|
||||||
|
y={block[1]}
|
||||||
|
animate={{ opacity: [0, 1, 0] }}
|
||||||
|
transition={{ duration: 1, times: [0, 0, 1] }}
|
||||||
|
onAnimationComplete={() => {
|
||||||
|
setHoveredBlocks((blocks) =>
|
||||||
|
blocks.filter((b) => b[2] !== block[2])
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
<defs>
|
||||||
|
<pattern
|
||||||
|
id={id}
|
||||||
|
width="96"
|
||||||
|
height="480"
|
||||||
|
x="50%"
|
||||||
|
patternUnits="userSpaceOnUse"
|
||||||
|
patternTransform={`translate(0 ${yOffset})`}
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path d="M128 0 98.572 147.138A16 16 0 0 1 82.883 160H13.117a16 16 0 0 0-15.69 12.862l-26.855 134.276A16 16 0 0 1-45.117 320H-116M64-160 34.572-12.862A16 16 0 0 1 18.883 0h-69.766a16 16 0 0 0-15.69 12.862l-26.855 134.276A16 16 0 0 1-109.117 160H-180M192 160l-29.428 147.138A15.999 15.999 0 0 1 146.883 320H77.117a16 16 0 0 0-15.69 12.862L34.573 467.138A16 16 0 0 1 18.883 480H-52M-136 480h58.883a16 16 0 0 0 15.69-12.862l26.855-134.276A16 16 0 0 1-18.883 320h69.766a16 16 0 0 0 15.69-12.862l26.855-134.276A16 16 0 0 1 109.117 160H192M-72 640h58.883a16 16 0 0 0 15.69-12.862l26.855-134.276A16 16 0 0 1 45.117 480h69.766a15.999 15.999 0 0 0 15.689-12.862l26.856-134.276A15.999 15.999 0 0 1 173.117 320H256M-200 320h58.883a15.999 15.999 0 0 0 15.689-12.862l26.856-134.276A16 16 0 0 1-82.883 160h69.766a16 16 0 0 0 15.69-12.862L29.427 12.862A16 16 0 0 1 45.117 0H128" />
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import clsx from "clsx"
|
||||||
|
|
||||||
|
import MenuIcon from "../icons/Menu"
|
||||||
|
import XIcon from "../icons/X"
|
||||||
|
import { Container } from "./Container"
|
||||||
|
import { Logo } from "./Logo"
|
||||||
|
|
||||||
|
type HeaderProps = {
|
||||||
|
panelId: string
|
||||||
|
expanded: boolean
|
||||||
|
onToggle: () => void
|
||||||
|
toggleRef: React.RefObject<HTMLButtonElement>
|
||||||
|
invert?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Header({
|
||||||
|
panelId,
|
||||||
|
expanded,
|
||||||
|
onToggle,
|
||||||
|
toggleRef,
|
||||||
|
invert = false,
|
||||||
|
}: HeaderProps) {
|
||||||
|
const Icon = expanded ? XIcon : MenuIcon
|
||||||
|
const iconClasses = clsx(
|
||||||
|
"h-6 w-6",
|
||||||
|
invert
|
||||||
|
? "fill-white group-hover:fill-neutral-200"
|
||||||
|
: "fill-neutral-950 group-hover:fill-neutral-700"
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<a href="/" aria-label="Home">
|
||||||
|
<Logo className="h-8" invert={invert} />
|
||||||
|
</a>
|
||||||
|
<div className="flex items-center gap-x-8">
|
||||||
|
<button
|
||||||
|
ref={toggleRef}
|
||||||
|
type="button"
|
||||||
|
onClick={onToggle}
|
||||||
|
aria-expanded={expanded ? "true" : "false"}
|
||||||
|
aria-controls={panelId}
|
||||||
|
className={clsx(
|
||||||
|
"group -m-2.5 rounded-full p-2.5 transition",
|
||||||
|
invert ? "hover:bg-white/10" : "hover:bg-neutral-950/10"
|
||||||
|
)}
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<Icon className={iconClasses} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
import clsx from "clsx";
|
import clsx from "clsx"
|
||||||
|
|
||||||
import LogoMark from "../icons/Logomark";
|
import LogoMark from "../icons/Logomark"
|
||||||
|
|
||||||
export function Logo({
|
export function Logo({
|
||||||
invert = false,
|
invert = false,
|
||||||
|
@ -15,11 +15,11 @@ export function Logo({
|
||||||
<p
|
<p
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-display text-xl font-bold tracking-tight",
|
"font-display text-xl font-bold tracking-tight",
|
||||||
invert ? "text-white" : "text-neutral-950",
|
invert ? "text-white" : "text-neutral-950"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
BoardWise
|
BoardWise
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import clsx from "clsx"
|
||||||
|
import { motion, MotionConfig, useReducedMotion } from "framer-motion"
|
||||||
|
|
||||||
|
import { Container } from "./Container"
|
||||||
|
import { Footer } from "./Footer"
|
||||||
|
import { GridPattern } from "./GridPattern"
|
||||||
|
import { Header } from "./Header"
|
||||||
|
|
||||||
|
function NavigationRow({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="even:mt-px sm:bg-neutral-950">
|
||||||
|
<Container>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2">{children}</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function NavigationItem({
|
||||||
|
href,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
href: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
className="group relative isolate -mx-6 bg-neutral-950 px-6 py-10 even:mt-px sm:mx-0 sm:px-0 sm:py-16 sm:odd:pr-16 sm:even:mt-0 sm:even:border-l sm:even:border-neutral-800 sm:even:pl-16"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<span className="absolute inset-y-0 -z-10 w-screen bg-neutral-900 opacity-0 transition group-odd:right-0 group-even:left-0 group-hover:opacity-100" />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Navigation() {
|
||||||
|
return (
|
||||||
|
<nav className="mt-px font-display text-5xl font-medium tracking-tight text-white">
|
||||||
|
<NavigationRow>
|
||||||
|
<NavigationItem href="/about/">About Us</NavigationItem>
|
||||||
|
</NavigationRow>
|
||||||
|
<NavigationRow>
|
||||||
|
<NavigationItem href="/contact/">Contact Us</NavigationItem>
|
||||||
|
</NavigationRow>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
let panelId = React.useId()
|
||||||
|
let [expanded, setExpanded] = React.useState(false)
|
||||||
|
let openRef = React.useRef<React.ElementRef<"button">>(null)
|
||||||
|
let closeRef = React.useRef<React.ElementRef<"button">>(null)
|
||||||
|
let shouldReduceMotion = useReducedMotion()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MotionConfig
|
||||||
|
transition={{
|
||||||
|
ease: "easeInOut",
|
||||||
|
duration: shouldReduceMotion ? 0 : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<header>
|
||||||
|
<div
|
||||||
|
className="absolute left-0 right-0 top-2 z-40 pt-6"
|
||||||
|
aria-hidden={expanded ? "true" : undefined}
|
||||||
|
// @ts-ignore (https://github.com/facebook/react/issues/17157)
|
||||||
|
inert={expanded ? "" : undefined}
|
||||||
|
>
|
||||||
|
<Header
|
||||||
|
panelId={panelId}
|
||||||
|
toggleRef={openRef}
|
||||||
|
expanded={expanded}
|
||||||
|
onToggle={() => {
|
||||||
|
setExpanded((expanded) => !expanded)
|
||||||
|
window.setTimeout(
|
||||||
|
() => closeRef.current?.focus({ preventScroll: true })
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
layout
|
||||||
|
id={panelId}
|
||||||
|
style={{ height: expanded ? "auto" : "1px" }}
|
||||||
|
className="relative z-50 overflow-hidden bg-neutral-950"
|
||||||
|
aria-hidden={expanded ? undefined : "true"}
|
||||||
|
// @ts-ignore (https://github.com/facebook/react/issues/17157)
|
||||||
|
inert={expanded ? undefined : ""}
|
||||||
|
>
|
||||||
|
<div className="bg-neutral-800">
|
||||||
|
<div className="bg-neutral-950 py-8">
|
||||||
|
<Header
|
||||||
|
invert
|
||||||
|
panelId={panelId}
|
||||||
|
toggleRef={closeRef}
|
||||||
|
expanded={expanded}
|
||||||
|
onToggle={() => {
|
||||||
|
setExpanded((expanded) => !expanded)
|
||||||
|
window.setTimeout(
|
||||||
|
() => openRef.current?.focus({ preventScroll: true })
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Navigation />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</header>
|
||||||
|
</MotionConfig>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clsx("relative flex flex-auto overflow-hidden bg-white", {
|
||||||
|
"pt-14": !expanded,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx("relative isolate flex w-full flex-col", {
|
||||||
|
"pt-9": !expanded,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<GridPattern
|
||||||
|
className="absolute inset-x-0 -top-14 -z-10 h-[1000px] w-full fill-neutral-50 stroke-neutral-950/5 [mask-image:linear-gradient(to_bottom_left,white_40%,transparent_50%)]"
|
||||||
|
yOffset={-96}
|
||||||
|
interactive
|
||||||
|
/>
|
||||||
|
|
||||||
|
<main className="w-full flex-auto">{children}</main>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
|
|
||||||
const SvgComponent = ({ invert = false, size = 25, ...props }) => {
|
const SvgComponent = ({ invert = false, size = 25, ...props }) => {
|
||||||
const color = invert ? "rgb(255, 255, 255)" : "rgb(10 10 10)";
|
const color = invert ? "rgb(255, 255, 255)" : "rgb(10 10 10)"
|
||||||
const radius = 5;
|
const radius = 5
|
||||||
const roundedTopRightPath = `
|
const roundedTopRightPath = `
|
||||||
M ${size / 2} 0
|
M ${size / 2} 0
|
||||||
H ${size - radius}
|
H ${size - radius}
|
||||||
Q ${size} 0, ${size} ${radius}
|
Q ${size} 0, ${size} ${radius}
|
||||||
V ${size / 2}
|
V ${size / 2}
|
||||||
H ${size / 2}
|
H ${size / 2}
|
||||||
Z`;
|
Z`
|
||||||
const roundedBottomLeftPath = `
|
const roundedBottomLeftPath = `
|
||||||
M 0 ${size - radius}
|
M 0 ${size - radius}
|
||||||
Q 0 ${size}, ${radius} ${size}
|
Q 0 ${size}, ${radius} ${size}
|
||||||
H ${size / 2}
|
H ${size / 2}
|
||||||
V ${size / 2}
|
V ${size / 2}
|
||||||
H 0
|
H 0
|
||||||
Z`;
|
Z`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
@ -28,6 +28,7 @@ const SvgComponent = ({ invert = false, size = 25, ...props }) => {
|
||||||
<path d={roundedTopRightPath} fill={color} />
|
<path d={roundedTopRightPath} fill={color} />
|
||||||
<path d={roundedBottomLeftPath} fill={color} />
|
<path d={roundedBottomLeftPath} fill={color} />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
export default SvgComponent;
|
|
||||||
|
export default SvgComponent
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
const SvgComponent = ({ ...props }) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
aria-hidden="true"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M2 6h20v2H2zM2 16h20v2H2z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SvgComponent
|
|
@ -0,0 +1,9 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
const SvgComponent = ({ ...props }) => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1920" {...props}>
|
||||||
|
<path d="M797.32 985.882 344.772 1438.43l188.561 188.562 452.549-452.549 452.548 452.549 188.562-188.562-452.549-452.548 452.549-452.549-188.562-188.561L985.882 797.32 533.333 344.772 344.772 533.333z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SvgComponent
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react'
|
import * as React from "react"
|
||||||
|
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from "react-dom/client"
|
||||||
import App from './App'
|
import App from "./App"
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('mount')!).render(<App />)
|
ReactDOM.createRoot(document.getElementById("mount")!).render(<App />)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { createBrowserRouter } from "react-router-dom"
|
||||||
|
|
||||||
|
export const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <div />,
|
||||||
|
},
|
||||||
|
])
|
|
@ -4,6 +4,24 @@
|
||||||
|
|
||||||
let
|
let
|
||||||
sources = {
|
sources = {
|
||||||
|
"@emotion/is-prop-valid-0.8.8" = {
|
||||||
|
name = "_at_emotion_slash_is-prop-valid";
|
||||||
|
packageName = "@emotion/is-prop-valid";
|
||||||
|
version = "0.8.8";
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz";
|
||||||
|
sha512 = "u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"@emotion/memoize-0.7.4" = {
|
||||||
|
name = "_at_emotion_slash_memoize";
|
||||||
|
packageName = "@emotion/memoize";
|
||||||
|
version = "0.7.4";
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz";
|
||||||
|
sha512 = "Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==";
|
||||||
|
};
|
||||||
|
};
|
||||||
"@remix-run/router-1.13.1" = {
|
"@remix-run/router-1.13.1" = {
|
||||||
name = "_at_remix-run_slash_router";
|
name = "_at_remix-run_slash_router";
|
||||||
packageName = "@remix-run/router";
|
packageName = "@remix-run/router";
|
||||||
|
@ -22,6 +40,15 @@ let
|
||||||
sha512 = "rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==";
|
sha512 = "rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
"framer-motion-10.16.12" = {
|
||||||
|
name = "framer-motion";
|
||||||
|
packageName = "framer-motion";
|
||||||
|
version = "10.16.12";
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.12.tgz";
|
||||||
|
sha512 = "w7Yzx0OzQ5Uh6uNkxaX+4TuAPuOKz3haSbjmHpdrqDpGuCJCpq6YP9Dy7JJWdZ6mJjndrg3Ao3vUwDajKNikCA==";
|
||||||
|
};
|
||||||
|
};
|
||||||
"js-tokens-4.0.0" = {
|
"js-tokens-4.0.0" = {
|
||||||
name = "js-tokens";
|
name = "js-tokens";
|
||||||
packageName = "js-tokens";
|
packageName = "js-tokens";
|
||||||
|
@ -85,6 +112,15 @@ let
|
||||||
sha512 = "CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==";
|
sha512 = "CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
"tslib-2.6.2" = {
|
||||||
|
name = "tslib";
|
||||||
|
packageName = "tslib";
|
||||||
|
version = "2.6.2";
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz";
|
||||||
|
sha512 = "AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
args = {
|
args = {
|
||||||
name = "boardwise";
|
name = "boardwise";
|
||||||
|
@ -92,8 +128,11 @@ let
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
sources."@emotion/is-prop-valid-0.8.8"
|
||||||
|
sources."@emotion/memoize-0.7.4"
|
||||||
sources."@remix-run/router-1.13.1"
|
sources."@remix-run/router-1.13.1"
|
||||||
sources."clsx-2.0.0"
|
sources."clsx-2.0.0"
|
||||||
|
sources."framer-motion-10.16.12"
|
||||||
sources."js-tokens-4.0.0"
|
sources."js-tokens-4.0.0"
|
||||||
sources."loose-envify-1.4.0"
|
sources."loose-envify-1.4.0"
|
||||||
sources."react-18.2.0"
|
sources."react-18.2.0"
|
||||||
|
@ -101,6 +140,7 @@ let
|
||||||
sources."react-router-6.20.1"
|
sources."react-router-6.20.1"
|
||||||
sources."react-router-dom-6.20.1"
|
sources."react-router-dom-6.20.1"
|
||||||
sources."scheduler-0.23.0"
|
sources."scheduler-0.23.0"
|
||||||
|
sources."tslib-2.6.2"
|
||||||
];
|
];
|
||||||
buildInputs = globalBuildInputs;
|
buildInputs = globalBuildInputs;
|
||||||
meta = {
|
meta = {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,15 +3,19 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
"framer-motion": "^10.16.12",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.20.1"
|
"react-router-dom": "^6.20.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@types/react": "^18.2.40",
|
"@types/react": "^18.2.40",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^18.2.17",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"typescript": "^5.3.2"
|
"autoprefixer": "^10.4.16",
|
||||||
|
"eslint-plugin-tailwindcss": "^3.13.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.7"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/** @type {import('prettier').Options} */
|
||||||
|
module.exports = {
|
||||||
|
arrowParens: "always",
|
||||||
|
semi: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
trailingComma: "es5",
|
||||||
|
plugins: ["prettier-plugin-tailwindcss"],
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
// See the Tailwind configuration guide for advanced usage
|
||||||
|
// https://tailwindcss.com/docs/configuration
|
||||||
|
|
||||||
|
const plugin = require("tailwindcss/plugin")
|
||||||
|
const defaultTheme = require("tailwindcss/defaultTheme")
|
||||||
|
const fs = require("fs")
|
||||||
|
const path = require("path")
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./js/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"../lib/boardwise_web.ex",
|
||||||
|
"../lib/boardwise_web/**/*.*ex",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
fontSize: {
|
||||||
|
xs: ["0.75rem", { lineHeight: "1rem" }],
|
||||||
|
sm: ["0.875rem", { lineHeight: "1.5rem" }],
|
||||||
|
base: ["1rem", { lineHeight: "1.75rem" }],
|
||||||
|
lg: ["1.125rem", { lineHeight: "1.75rem" }],
|
||||||
|
xl: ["1.25rem", { lineHeight: "2rem" }],
|
||||||
|
"2xl": ["1.5rem", { lineHeight: "2.25rem" }],
|
||||||
|
"3xl": ["1.75rem", { lineHeight: "2.25rem" }],
|
||||||
|
"4xl": ["2rem", { lineHeight: "2.5rem" }],
|
||||||
|
"5xl": ["2.5rem", { lineHeight: "3rem" }],
|
||||||
|
"6xl": ["3rem", { lineHeight: "3.5rem" }],
|
||||||
|
"7xl": ["4rem", { lineHeight: "4.5rem" }],
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
backgroundImage: {
|
||||||
|
"radial-gradient/black":
|
||||||
|
"radial-gradient(circle at center, black 0%, transparent 50%)",
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
"4xl": "2.5rem",
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
sm: "0 2px 4px 0 rgb(60 72 88 / 0.15)",
|
||||||
|
DEFAULT: "0 0 3px rgb(60 72 88 / 0.15)",
|
||||||
|
md: "0 5px 13px rgb(60 72 88 / 0.20)",
|
||||||
|
lg: "0 10px 25px -3px rgb(60 72 88 / 0.15)",
|
||||||
|
xl: "0 20px 25px -5px rgb(60 72 88 / 0.1), 0 8px 10px -6px rgb(60 72 88 / 0.1)",
|
||||||
|
"2xl": "0 25px 50px -12px rgb(60 72 88 / 0.25)",
|
||||||
|
inner: "inset 0 2px 4px 0 rgb(60 72 88 / 0.05)",
|
||||||
|
testi: "2px 2px 2px -1px rgb(60 72 88 / 0.15)",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
display: [
|
||||||
|
["Mona Sans", ...defaultTheme.fontFamily.sans],
|
||||||
|
{ fontVariationSettings: '"wdth" 125' },
|
||||||
|
],
|
||||||
|
sans: ["Mona Sans", ...defaultTheme.fontFamily.sans],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require("autoprefixer"),
|
||||||
|
require("tailwindcss"),
|
||||||
|
require("@tailwindcss/forms"),
|
||||||
|
// Allows prefixing tailwind classes with LiveView classes to add rules
|
||||||
|
// only when LiveView classes are applied, for example:
|
||||||
|
//
|
||||||
|
// <div class="phx-click-loading:animate-ping">
|
||||||
|
//
|
||||||
|
plugin(({ addVariant }) =>
|
||||||
|
addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])
|
||||||
|
),
|
||||||
|
plugin(({ addVariant }) =>
|
||||||
|
addVariant("phx-click-loading", [
|
||||||
|
".phx-click-loading&",
|
||||||
|
".phx-click-loading &",
|
||||||
|
])
|
||||||
|
),
|
||||||
|
plugin(({ addVariant }) =>
|
||||||
|
addVariant("phx-submit-loading", [
|
||||||
|
".phx-submit-loading&",
|
||||||
|
".phx-submit-loading &",
|
||||||
|
])
|
||||||
|
),
|
||||||
|
plugin(({ addVariant }) =>
|
||||||
|
addVariant("phx-change-loading", [
|
||||||
|
".phx-change-loading&",
|
||||||
|
".phx-change-loading &",
|
||||||
|
])
|
||||||
|
),
|
||||||
|
|
||||||
|
// Embeds Heroicons (https://heroicons.com) into your app.css bundle
|
||||||
|
// See your `CoreComponents.icon/1` for more information.
|
||||||
|
//
|
||||||
|
plugin(function ({ matchComponents, theme }) {
|
||||||
|
let iconsDir = path.join(__dirname, "./vendor/heroicons/optimized")
|
||||||
|
let values = {}
|
||||||
|
let icons = [
|
||||||
|
["", "/24/outline"],
|
||||||
|
["-solid", "/24/solid"],
|
||||||
|
["-mini", "/20/solid"],
|
||||||
|
]
|
||||||
|
icons.forEach(([suffix, dir]) => {
|
||||||
|
fs.readdirSync(path.join(iconsDir, dir)).forEach((file) => {
|
||||||
|
let name = path.basename(file, ".svg") + suffix
|
||||||
|
values[name] = { name, fullPath: path.join(iconsDir, dir, file) }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
matchComponents(
|
||||||
|
{
|
||||||
|
hero: ({ name, fullPath }) => {
|
||||||
|
let content = fs
|
||||||
|
.readFileSync(fullPath)
|
||||||
|
.toString()
|
||||||
|
.replace(/\r?\n|\r/g, "")
|
||||||
|
return {
|
||||||
|
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
|
||||||
|
"-webkit-mask": `var(--hero-${name})`,
|
||||||
|
mask: `var(--hero-${name})`,
|
||||||
|
"mask-repeat": "no-repeat",
|
||||||
|
"background-color": "currentColor",
|
||||||
|
"vertical-align": "middle",
|
||||||
|
display: "inline-block",
|
||||||
|
width: theme("spacing.5"),
|
||||||
|
height: theme("spacing.5"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ values }
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
|
@ -1,68 +0,0 @@
|
||||||
// See the Tailwind configuration guide for advanced usage
|
|
||||||
// https://tailwindcss.com/docs/configuration
|
|
||||||
|
|
||||||
const plugin = require("tailwindcss/plugin")
|
|
||||||
const fs = require("fs")
|
|
||||||
const path = require("path")
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
"./js/**/*.js",
|
|
||||||
"../lib/boardwise_web.ex",
|
|
||||||
"../lib/boardwise_web/**/*.*ex"
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
brand: "#FD4F00",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
require("@tailwindcss/forms"),
|
|
||||||
// Allows prefixing tailwind classes with LiveView classes to add rules
|
|
||||||
// only when LiveView classes are applied, for example:
|
|
||||||
//
|
|
||||||
// <div class="phx-click-loading:animate-ping">
|
|
||||||
//
|
|
||||||
plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
|
|
||||||
plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
|
|
||||||
plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
|
|
||||||
plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
|
|
||||||
|
|
||||||
// Embeds Heroicons (https://heroicons.com) into your app.css bundle
|
|
||||||
// See your `CoreComponents.icon/1` for more information.
|
|
||||||
//
|
|
||||||
plugin(function({matchComponents, theme}) {
|
|
||||||
let iconsDir = path.join(__dirname, "./vendor/heroicons/optimized")
|
|
||||||
let values = {}
|
|
||||||
let icons = [
|
|
||||||
["", "/24/outline"],
|
|
||||||
["-solid", "/24/solid"],
|
|
||||||
["-mini", "/20/solid"]
|
|
||||||
]
|
|
||||||
icons.forEach(([suffix, dir]) => {
|
|
||||||
fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
|
|
||||||
let name = path.basename(file, ".svg") + suffix
|
|
||||||
values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
matchComponents({
|
|
||||||
"hero": ({name, fullPath}) => {
|
|
||||||
let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
|
|
||||||
return {
|
|
||||||
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
|
|
||||||
"-webkit-mask": `var(--hero-${name})`,
|
|
||||||
"mask": `var(--hero-${name})`,
|
|
||||||
"mask-repeat": "no-repeat",
|
|
||||||
"background-color": "currentColor",
|
|
||||||
"vertical-align": "middle",
|
|
||||||
"display": "inline-block",
|
|
||||||
"width": theme("spacing.5"),
|
|
||||||
"height": theme("spacing.5")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {values})
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -68,7 +68,7 @@ config :tailwind,
|
||||||
version: "3.3.5",
|
version: "3.3.5",
|
||||||
default: [
|
default: [
|
||||||
args: ~w(
|
args: ~w(
|
||||||
--config=tailwind.config.js
|
--config=tailwind.config.cjs
|
||||||
--input=css/app.css
|
--input=css/app.css
|
||||||
--output=../priv/static/assets/app.css
|
--output=../priv/static/assets/app.css
|
||||||
),
|
),
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
tailwindcss = pkgs.nodePackages.tailwindcss.overrideAttrs (oa: {
|
tailwindcss = pkgs.nodePackages.tailwindcss.overrideAttrs (oa: {
|
||||||
plugins = [
|
plugins = [
|
||||||
|
pkgs.nodePackages.autoprefixer
|
||||||
pkgs.nodePackages."@tailwindcss/forms"
|
pkgs.nodePackages."@tailwindcss/forms"
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="h-full antialiased">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="BoardWise - Level up your chess game with a world-class coach."
|
||||||
|
/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="csrf-token" content={get_csrf_token()} />
|
||||||
|
<link rel="icon" href={~p"/favicon.ico"} type="image/x-icon" />
|
||||||
|
<title>BoardWise</title>
|
||||||
|
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||||
|
<script defer type="text/javascript" src={~p"/assets/react/main.js"}>
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="flex min-h-full flex-col text-base text-black">
|
||||||
|
<%= @inner_content %>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -15,8 +15,6 @@
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||||
</script>
|
</script>
|
||||||
<script defer type="text/javascript" src={~p"/assets/react/main.js"}>
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white antialiased">
|
<body class="bg-white antialiased">
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
defmodule BoardWiseWeb.ReactController do
|
defmodule BoardWiseWeb.ReactController do
|
||||||
use BoardWiseWeb, :controller
|
use BoardWiseWeb, :controller
|
||||||
|
|
||||||
def index(conn, _params) do
|
def mount(conn, _params) do
|
||||||
# Set `layout` to false to bypass the app layout. The goal here is to
|
# Set `layout` to false to bypass the app layout. The goal here is to
|
||||||
# eventually migrate away from the React app as is defined in favor of
|
# eventually migrate away from the React app as is defined in favor of
|
||||||
# Phoenix related components. Exposing this mount point is the first step
|
# Phoenix related components. Exposing this mount point is the first step
|
||||||
# in migrating away from Vercel into a self-hosted solution.
|
# in migrating away from Vercel into a self-hosted solution.
|
||||||
render(conn, :index, layout: false)
|
render(conn, :mount, layout: false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<div id="mount"></div>
|
|
|
@ -0,0 +1 @@
|
||||||
|
<div id="mount" class="flex min-h-full flex-col text-base text-black"></div>
|
|
@ -10,14 +10,16 @@ defmodule BoardWiseWeb.Router do
|
||||||
plug :put_secure_browser_headers
|
plug :put_secure_browser_headers
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pipeline :react do
|
||||||
|
plug :put_root_layout, html: {BoardWiseWeb.Layouts, :react}
|
||||||
|
end
|
||||||
|
|
||||||
pipeline :api do
|
pipeline :api do
|
||||||
plug :accepts, ["json"]
|
plug :accepts, ["json"]
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", BoardWiseWeb do
|
scope "/", BoardWiseWeb do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
get "/*path", ReactController, :index
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
# Other scopes may use custom stacks.
|
||||||
|
@ -41,4 +43,11 @@ defmodule BoardWiseWeb.Router do
|
||||||
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A catch-all that defers to the React app router.
|
||||||
|
scope "/", BoardWiseWeb do
|
||||||
|
pipe_through [:browser, :react]
|
||||||
|
|
||||||
|
get "/*path", ReactController, :mount
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue