feat(app): recipe ideas
This commit is contained in:
@@ -8,6 +8,7 @@ import IconGrid from "@/components/IconGrid";
|
||||
import Footer from "@/components/Footer";
|
||||
import ErrorBoundary from "@/components/ErrorBoundary";
|
||||
import Notice from "@/components/Notice";
|
||||
import Recipes from "@/components/Recipes";
|
||||
import {
|
||||
useIconParameters,
|
||||
usePersistSettings,
|
||||
@@ -47,6 +48,7 @@ const App: React.FC<any> = () => {
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</main>
|
||||
<Recipes />
|
||||
<Footer />
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
22
src/components/Recipes/Recipe.tsx
Normal file
22
src/components/Recipes/Recipe.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ArrowCircleUpRight } from "@phosphor-icons/react";
|
||||
|
||||
export type RecipeProps = {
|
||||
title: string;
|
||||
url: string;
|
||||
Example: () => JSX.Element;
|
||||
};
|
||||
|
||||
const Recipe = ({ title, url, Example }: RecipeProps) => {
|
||||
return (
|
||||
<a className="recipe card" href={url}>
|
||||
{/* <h1>{title}</h1> */}
|
||||
<div className="recipe-linkout">
|
||||
<span>Open on StackBlitz</span>
|
||||
<ArrowCircleUpRight weight="fill" size={32} />
|
||||
</div>
|
||||
<Example />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Recipe;
|
||||
39
src/components/Recipes/Recipes.css
Normal file
39
src/components/Recipes/Recipes.css
Normal file
@@ -0,0 +1,39 @@
|
||||
.recipes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(184px, 1fr));
|
||||
gap: 16px;
|
||||
padding-block: 32px;
|
||||
}
|
||||
|
||||
a.recipe {
|
||||
position: relative;
|
||||
color: initial;
|
||||
padding: 16px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.recipe-linkout {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
background-color: var(--soft);
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
|
||||
.recipe-linkout:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.example {
|
||||
display: grid;
|
||||
grid-template-columns: 64px 64px;
|
||||
gap: 12px;
|
||||
place-items: center;
|
||||
}
|
||||
29
src/components/Recipes/Recipes.tsx
Normal file
29
src/components/Recipes/Recipes.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { IconContext } from "@phosphor-icons/react";
|
||||
|
||||
import Recipe from "./Recipe";
|
||||
import items from "./items";
|
||||
import "./Recipes.css";
|
||||
|
||||
const Recipes = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="toolbar">
|
||||
<div className="toolbar-contents">
|
||||
<h2>Recipes</h2>
|
||||
<p>Cool stuff to do with Phosphor</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-container">
|
||||
<IconContext.Provider value={{ size: 64 }}>
|
||||
<div className="recipes grid">
|
||||
{items.map((itemProps) => (
|
||||
<Recipe key={itemProps.title} {...itemProps} />
|
||||
))}
|
||||
</div>
|
||||
</IconContext.Provider>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Recipes;
|
||||
1
src/components/Recipes/index.ts
Normal file
1
src/components/Recipes/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./Recipes";
|
||||
83
src/components/Recipes/items/Animation.tsx
Normal file
83
src/components/Recipes/items/Animation.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Cube } from "@phosphor-icons/react";
|
||||
|
||||
import { RecipeProps } from "../Recipe";
|
||||
|
||||
const recipe: RecipeProps = {
|
||||
title: "SVG Wizardry",
|
||||
url: "https://stackblitz.com/edit/react-ts-f7q7gs?file=App.tsx,style.css",
|
||||
Example() {
|
||||
return (
|
||||
<div className="example">
|
||||
<Cube
|
||||
color="darkorchid"
|
||||
weight="duotone"
|
||||
style={{ fill: "url(#star)" }}
|
||||
>
|
||||
<defs>
|
||||
<pattern id="star" viewBox="0,0,10,10" width="10%" height="10%">
|
||||
<polygon
|
||||
points="0,0 2,5 0,10 5,8 10,10 8,5 10,0 5,2"
|
||||
fill="darkOrchid"
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
</Cube>
|
||||
<Cube
|
||||
color="darkorchid"
|
||||
weight="duotone"
|
||||
style={{ filter: "url(#emboss)" }}
|
||||
>
|
||||
<filter id="emboss">
|
||||
<feConvolveMatrix
|
||||
kernelMatrix="
|
||||
5 0 0
|
||||
0 0 0
|
||||
0 0 -3
|
||||
"
|
||||
/>
|
||||
</filter>
|
||||
</Cube>
|
||||
<Cube color="darkorchid" weight="duotone">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0;1;0"
|
||||
dur="4s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
dur="5s"
|
||||
from="0 0 0"
|
||||
to="360 0 0"
|
||||
repeatCount="indefinite"
|
||||
></animateTransform>
|
||||
</Cube>
|
||||
<Cube
|
||||
color="darkorchid"
|
||||
weight="duotone"
|
||||
style={{ filter: "url(#displacementFilter)" }}
|
||||
>
|
||||
<filter id="displacementFilter">
|
||||
<feTurbulence
|
||||
type="turbulence"
|
||||
baseFrequency="0.01"
|
||||
numOctaves="3"
|
||||
result="turbulence"
|
||||
/>
|
||||
<feDisplacementMap
|
||||
in2="turbulence"
|
||||
in="SourceGraphic"
|
||||
scale="20"
|
||||
xChannelSelector="B"
|
||||
yChannelSelector="G"
|
||||
/>
|
||||
</filter>
|
||||
</Cube>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default recipe;
|
||||
63
src/components/Recipes/items/Duocolor.tsx
Normal file
63
src/components/Recipes/items/Duocolor.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Icon,
|
||||
IconProps,
|
||||
Barricade,
|
||||
GasCan,
|
||||
IceCream,
|
||||
FlyingSaucer,
|
||||
} from "@phosphor-icons/react";
|
||||
|
||||
import { RecipeProps } from "../Recipe";
|
||||
|
||||
type DuocolorProps = Omit<IconProps, "weight"> & {
|
||||
Icon: Icon;
|
||||
duocolor?: string;
|
||||
};
|
||||
|
||||
function Duocolor({ Icon, duocolor, ...iconProps }: DuocolorProps) {
|
||||
const [uuid, style] = useMemo(() => {
|
||||
// UUID to make sure the inline stylesheet is "scoped" to this icon only.
|
||||
// Could also easily be implemented with a regular CSS selector.
|
||||
const uuid = "ph-" + Math.floor(Math.random() * 1_000_000).toString(16);
|
||||
// const uuid = "ph-" + crypto.randomUUID();
|
||||
return [uuid, !duocolor ? null : createDuocolorStyle(uuid, duocolor)];
|
||||
}, [duocolor]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{style}
|
||||
<Icon {...iconProps} weight="duotone" data-ph={uuid} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function createDuocolorStyle(id: string, color: string) {
|
||||
return (
|
||||
<style>
|
||||
{`
|
||||
[data-ph="${id}"] [opacity="0.2"] {
|
||||
opacity: 1;
|
||||
fill: ${color};
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
);
|
||||
}
|
||||
|
||||
const recipe: RecipeProps = {
|
||||
title: "Duocolor",
|
||||
url: "https://stackblitz.com/edit/react-ts-kvdzu1?file=App.tsx",
|
||||
Example() {
|
||||
return (
|
||||
<div className="example">
|
||||
<Duocolor Icon={FlyingSaucer} duocolor="darkcyan" />
|
||||
<Duocolor Icon={Barricade} color="darkgray" duocolor="orange" />
|
||||
<Duocolor Icon={IceCream} color="saddlebrown" duocolor="lightpink" />
|
||||
<Duocolor Icon={GasCan} duocolor="indianred" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default recipe;
|
||||
6
src/components/Recipes/items/index.ts
Normal file
6
src/components/Recipes/items/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import animation from "./Animation";
|
||||
import duocolor from "./Duocolor";
|
||||
import { RecipeProps } from "../Recipe";
|
||||
|
||||
const items: RecipeProps[] = [animation, duocolor, duocolor, animation];
|
||||
export default items;
|
||||
@@ -1,9 +1,10 @@
|
||||
nav.toolbar {
|
||||
.toolbar {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: white;
|
||||
background-color: var(--eggplant);
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user