Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Recipe Block #2323

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2d79307
create recipe block PoC
joshbermanssw Oct 14, 2024
7ca9a73
highlight color
joshbermanssw Oct 16, 2024
ea77fec
ui changes
joshbermanssw Oct 30, 2024
21f7d53
1st attempt to make sticky
joshbermanssw Oct 30, 2024
987ad45
fix sticky
joshbermanssw Oct 30, 2024
9b8448e
more ui
joshbermanssw Oct 31, 2024
a0a8fa4
lhs rhs height sizing
joshbermanssw Nov 1, 2024
4fb06c6
add instruction click scroll to line
joshbermanssw Nov 1, 2024
381e4d1
rm lhs useRef
joshbermanssw Nov 1, 2024
edc5c24
fix LHS heighting
joshbermanssw Nov 1, 2024
fc8f77a
Recipe rendering in docs too as .mdx
joshbermanssw Nov 1, 2024
00687fe
sm and md view #1
joshbermanssw Nov 1, 2024
5e4c3d4
highlight lines styling back
joshbermanssw Nov 1, 2024
ca71d7f
change tooltip
joshbermanssw Nov 1, 2024
fa1b15c
template modifictions
joshbermanssw Nov 1, 2024
1bb6f7d
docAndBlog fixes
joshbermanssw Nov 1, 2024
6397c15
make tool bar sticky
joshbermanssw Nov 1, 2024
e475b25
change codeblock name
joshbermanssw Nov 1, 2024
e61180f
handle down click arrow
joshbermanssw Nov 1, 2024
d6c2abc
bg-color match
joshbermanssw Nov 1, 2024
86ddc7e
down arrow
joshbermanssw Nov 1, 2024
438673c
fixed button and void overlay
joshbermanssw Nov 3, 2024
7386a90
extrapolate hidden logic
joshbermanssw Nov 3, 2024
5a6c891
upd z-index
joshbermanssw Nov 4, 2024
e85972d
rm index for RecipeBlock
joshbermanssw Nov 4, 2024
59b2f2a
fix rounding issues
joshbermanssw Nov 4, 2024
05cf771
rm border
joshbermanssw Nov 4, 2024
5fb4993
fix scrolling
joshbermanssw Nov 4, 2024
2b8a080
TinaCMS content update
tina-cloud-app[bot] Nov 5, 2024
63bc6ec
closing animation
joshbermanssw Nov 6, 2024
09e5c02
remove unneccessary content
joshbermanssw Nov 6, 2024
32ad732
use vars instead of arbitrary numbers
joshbermanssw Nov 6, 2024
4166a66
add comment
joshbermanssw Nov 6, 2024
a43feca
extrapolate styles
joshbermanssw Nov 6, 2024
f5e95be
fix re-render
joshbermanssw Nov 6, 2024
7c6a95e
logic to hide the down button when the container isnt scrollable
joshbermanssw Nov 6, 2024
c638276
comments
joshbermanssw Nov 6, 2024
3df6cbc
extrapolate all components
joshbermanssw Nov 6, 2024
9a29fa6
move extrapolated components to different dir
joshbermanssw Nov 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions components/blocks/Blocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { TinaBanner } from './TinaBanner';
import { HighlightsSection } from './HighlightsSection';
import { SpacerComponent } from './Spacer';
import { CarouselFeatureBlock } from './CarouselFeature';
import RecipeBlock from './Recipe';

export const Blocks = ({
blocks,
Expand Down Expand Up @@ -82,6 +83,10 @@ export const Blocks = ({
return (
<ColumnsBlock key={`block-${index}`} data={block} index={index} />
);
case 'PageBlocksRecipeBlock':
return (
<RecipeBlock data={block}/>
)
case 'PageBlocksShowcase':
return (
<ShowcaseItemsBlock
Expand Down
56 changes: 56 additions & 0 deletions components/blocks/Recipe.template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export const RecipeBlock = {
name: 'recipeBlock',
label: 'Recipe Block',
fields: [
{
name: 'title',
label: 'Heading Title',
type: 'string',
},
{
name: 'description',
label: 'Description',
type: 'string',
},
{
name: 'codeblock',
label: 'Code Block',
type: 'rich-text',
},
{
name: 'instruction',
label: 'Instruction',
type: 'object',
list: true,
ui: {
itemProps: (item) => {
return { label: item?.header }
},
},
fields: [
{
name: 'header',
label: 'Header',
type: 'string',
},
{
name: 'itemDescription',
label: 'Item Description',
type: 'string',
},
{
name: 'codeLineStart',
label: 'Code Line Start',
type: 'number',
description: 'Please note that if you enter negative values, it will highlight from 0 to your end number'
},
{
name: 'codeLineEnd',
label: 'Code Line End',
type: 'number',
description: 'Please note that highlighting will not work if your end number is > than your start number'
}
],
},
],
};
200 changes: 200 additions & 0 deletions components/blocks/Recipe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import React, { useEffect, useRef, useState } from 'react';
import { FaChevronCircleDown } from 'react-icons/fa';
import { TinaMarkdown } from 'tinacms/dist/rich-text';
import { customHighlightCSS } from '../styles/RecipeCSS';
import { CodeToolbar } from '../ui/recipeComponent/RecipeCodeToolBar';
import CodeBlockWithHighlightLines from '../ui/recipeComponent/RecipeCodeBlockWithHighlight';

export const RecipeBlock = ({ data }) => {
const { title, description, codeblock, instruction } = data;

const [highlightLines, setHighlightLines] = useState('');
const [clickedInstruction, setClickedInstruction] = useState<number | null>(
null
);
//LHSheight is the height used for the instructions block when the screen is >= 1024px
const [LHSheight, setLHSheight] = useState<string | null>(null);
const [CodeBlockWidth, setCodeBlockWidth] = useState<string | null>(null);
const [isBottomOfInstructions, setIsBottomOfInstructions] =
useState<boolean>(false);

const codeblockRef = useRef<HTMLDivElement>(null);
const instructionBlockRefs = useRef<HTMLDivElement>(null); //the entire instructions container
const instructionRefs = useRef<(HTMLDivElement | null)[]>([]); //list of individual objects in the instruction block

useEffect(() => {
const style = document.createElement('style');
style.textContent = customHighlightCSS;
document.head.appendChild(style);

return () => {
document.head.removeChild(style);
};
}, [highlightLines]);

useEffect(() => {
setLHSheight(`${codeblockRef.current?.offsetHeight}`);
setCodeBlockWidth(`${codeblockRef.current?.offsetWidth}`);
});

const checkIfBottom = (event: React.UIEvent<HTMLDivElement>) => {
const { scrollHeight, scrollTop, clientHeight } = event.currentTarget;
setIsBottomOfInstructions(scrollHeight - scrollTop <= clientHeight + 10);
};

const handleInstructionClick = (
index: number,
codeLineStart?: number,
codeLineEnd?: number
) => {
setHighlightLines(`${codeLineStart}-${codeLineEnd}`);
setClickedInstruction(index === clickedInstruction ? null : index);

const linePixelheight = 24;
const linePixelBuffer = 15; // gives the moving logic some breathing room

if (codeblockRef.current) {
codeblockRef.current.scrollTo({
top: linePixelheight * codeLineStart - linePixelBuffer,
behavior: 'smooth',
});
}

if (window.innerWidth < 1024 && instructionRefs.current[index]) {
instructionRefs.current[index].scrollIntoView({
behavior: 'smooth',
block: 'nearest',
});
}
};

const handleDownArrowClick = () => {
const lastInstruction =
instructionRefs.current[instructionRefs.current.length - 1];
if (lastInstruction) {
lastInstruction.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
});
}
};

//height used for the instructions container when the screen is < 1024px. Maintains 1:2 ratio of instruction to code
const smAndMbHeight = LHSheight ? `${Number(LHSheight) / 2}px` : null;

const calculateInstructionsHeight = () => {
return instructionRefs.current.reduce((total, ref) => {
return total + (ref?.offsetHeight || 0);
}, 0);
};

const checkIfScrollable = () => {
if (typeof window !== 'undefined' && window.innerWidth < 1024) {
return (
calculateInstructionsHeight() >= parseInt(smAndMbHeight || '0', 10) //this is necessary because the smAndMbHeight actually has a 'px' suffix, parseInt will remove it
);
} else {
return calculateInstructionsHeight() > parseInt(LHSheight || '0', 10);
}
};


return (
<div className="recipe-block-container mt-20 relative">
<div className="title-description px-10">
<h2 className="font-tuner text-orange-500 text-2xl">
{title || 'Default Title'}
</h2>
<p className="font-light py-2 text-base">
{description || 'Default Description'}
</p>
</div>

<div className="content-wrapper flex flex-col lg:flex-row px-10 items-stretch">
<div
className="instructions bg-gray-800 relative lg:w-1/3 max-h-50vh flex-shrink-0 flex-grow rounded-tl-xl rounded-br-xl lg:rounded-br-none rounded-tr-xl lg:rounded-tr-none lg:rounded-bl-xl flex flex-col"
ref={instructionBlockRefs}
style={{
height:
typeof window !== 'undefined' && window.innerWidth >= 1024
? `${LHSheight}px`
: `${smAndMbHeight}`,
}}
>
<div className={`${isBottomOfInstructions ? 'hidden' : ''}`}>
<div
className={`absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black opacity-60 lg:rounded-bl-xl pointer-events-none `}
></div>
<FaChevronCircleDown
onClick={handleDownArrowClick}
className={`absolute bottom-4 left-1/2 transform -translate-x-1/2 w-7 h-7 text-xl text-white cursor-pointer shadow-md
${checkIfScrollable() ? '' : 'hidden'}`}
/>
</div>

<div
className="overflow-auto rounded-tl-xl rounded-bl-xl rounded-tr-xl lg:rounded-tr-none"
onScroll={checkIfBottom}
>
{instruction?.map((inst, idx) => (
<div
key={idx}
ref={(el) => (instructionRefs.current[idx] = el)}
className={`instruction-item cursor-pointer p-4 border-gray-700 border-y bg-gray-800 text-white
${clickedInstruction === idx ? 'bg-slate-600' : ''} `}
onClick={() =>
handleInstructionClick(
idx,
inst.codeLineStart,
inst.codeLineEnd
)
}
>
<h5 className="font-tuner">{`${idx + 1}. ${
inst.header || 'Default Header'
}`}</h5>
<div
className={`overflow-auto transition-all ease-in-out ${
clickedInstruction === idx
? 'duration-500 max-h-full opacity-100'
: 'duration-0 max-h-0 opacity-0'
}`}
>
<span className="mt-2">
{inst.itemDescription || 'Default Item Description'}
</span>
</div>
</div>
)) || <p>No instructions available.</p>}
</div>
</div>

<div
ref={codeblockRef}
className="codeblock bg-gray-800 lg:w-2/3 max-h-50vh overflow-auto lg:rounded-tr-xl rounded-bl-xl lg:rounded-bl-none rounded-br-xl "
>
{codeblock ? (
<div>
<TinaMarkdown
key={highlightLines}
content={codeblock}
components={{
code_block: (props) => (
<CodeBlockWithHighlightLines
{...props}
highlightLines={highlightLines}
/>
),
}}
/>
</div>
) : (
<p>No code block available.</p>
)}
Comment on lines +178 to +193
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @JackDevAU
I dont like that we need to have a TinaMarkdown rich text field here to get the code block.

My suggested solution

  1. New custom Tina field - just a string but with a ui override with a nice code editor
  2. use that here instead of TinaMarkdown
image

</div>
</div>
</div>
);
};

export default RecipeBlock;
43 changes: 43 additions & 0 deletions components/styles/RecipeCSS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Custom CSS to override PrismJS themes
export const customHighlightCSS = `
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
color: white;
background: #111827;
}

pre[class*="language-"] > code[class*="language-"] {
position: relative;
}

pre[class*="language-"]{
padding: 0.5rem;
}

.line-numbers-rows > span:before {
content: counter(linenumber);
color: #9FFCEF;
display: block;
padding-right: 0.8em;
text-align: right;
}

.line-highlight {
background: rgba(71, 85, 105, 0.25);
}

.line-numbers .line-numbers-rows {
border-right: 1px solid #6B7280;
}

pre[class*="language-"] {
padding: 1em;
margin: 0 0 0.5em 0;
overflow: auto;
}

pre[class*="language-"] ::selection {
background: white;
color: black;
}
`;
Loading