Skip to content

Commit

Permalink
docs: show selected path if block scroll in view (#6079)
Browse files Browse the repository at this point in the history
Co-authored-by: PatrickJS <github@patrickjs.com>
  • Loading branch information
RaiVaibhav and PatrickJS committed May 12, 2024
1 parent e9012d1 commit 80ea7cf
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 43 deletions.
34 changes: 30 additions & 4 deletions packages/docs/src/components/code-block/code-block.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { component$, useStyles$ } from '@builder.io/qwik';
import { component$, useStyles$, type QRL, useVisibleTask$, useSignal } from '@builder.io/qwik';
import prismjs from 'prismjs';
// Set to global so that prism language plugins can find it.
const _global =
Expand All @@ -12,14 +12,37 @@ import 'prismjs/components/prism-jsx'; // needs PRISM global
import 'prismjs/components/prism-tsx'; // needs PRISM global

import styles from './code-block.css?inline';
import { CopyCode } from '../copy-code/copy-code-block';
interface CodeBlockProps {
path?: string;
language?: 'markup' | 'css' | 'javascript' | 'json' | 'jsx' | 'tsx';
code: string;
pathInView$?: QRL<(name: string) => void>;
observerRootId?: string;
}

export const CodeBlock = component$((props: CodeBlockProps) => {
const listSig = useSignal<Element>();
useStyles$(styles);

useVisibleTask$(async () => {
const { pathInView$, path, observerRootId } = props;
if (pathInView$ && path && listSig.value !== undefined) {
const el = listSig.value;
const intersectionObserver = new IntersectionObserver(
([{ isIntersecting }]) => isIntersecting && pathInView$(path),
{
//to avoid any edge case
root: observerRootId ? document.getElementById(observerRootId) : null,
}
);
intersectionObserver.observe(el);
return () => {
intersectionObserver.unobserve(el);
};
}
});

let language = props.language;
if (!language && props.path && props.code) {
const ext = props.path.split('.').pop();
Expand All @@ -37,9 +60,12 @@ export const CodeBlock = component$((props: CodeBlockProps) => {
const highlighted = prismjs.highlight(props.code, prismjs.languages[language], language);
const className = `language-${language}`;
return (
<pre class={className}>
<code class={className} dangerouslySetInnerHTML={highlighted} />
</pre>
<div class="relative">
<pre class={className} ref={listSig}>
<code class={className} dangerouslySetInnerHTML={highlighted} />
</pre>
<CopyCode code={props.code} />
</div>
);
}
return null;
Expand Down
64 changes: 64 additions & 0 deletions packages/docs/src/components/copy-code/copy-code-block.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
import { CopyCode as CopyCodeIcon } from '../svgs/copy-code-icon';
import styles from './copy-code.css?inline';

const Check = component$(({ height = 12, width = 12 }: { height?: number; width?: number }) => {
useStyles$(styles);

return (
<svg
class="w-3.5 h-3.5 text-white "
height={height}
width={width}
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 16 12"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 5.917 5.724 10.5 15 1.5"
/>
</svg>
);
});
export const CopyCode = component$(({ code }: { code: string }) => {
const copied = useSignal(false);
return (
<button
preventdefault:click
onClick$={async (e) => {
copied.value = !copied.value;
if (copied.value) {
setTimeout(() => (copied.value = false), 1500);
}
if (navigator.clipboard) {
await navigator.clipboard.writeText(code);
}
}}
class="absolute text-white right-2 top-2 shadow-2xl bg-[#1e1e1e] z-10"
>
<span
class={{
animate: true,
visible: copied.value,
hidden: !copied.value,
}}
>
<Check />
</span>
<span
class={{
animate: true,
visible: !copied.value,
hidden: copied.value,
}}
>
<CopyCodeIcon />
</span>
</button>
);
});
14 changes: 14 additions & 0 deletions packages/docs/src/components/copy-code/copy-code.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.animate {
opacity: 1;
animation: check-in 1 1s;
}

@keyframes check-in {
0% {
opacity: 0;
}

100% {
opacity: 1;
}
}
23 changes: 23 additions & 0 deletions packages/docs/src/components/svgs/copy-code-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
interface CopyIconProps {
width?: number;
height?: number;
}

export const CopyCode = ({ height = 18, width = 18 }: CopyIconProps) => {
return (
<svg
width={width}
height={height}
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
{' '}
<path stroke="none" d="M0 0h24v24H0z" /> <rect x="8" y="8" width="12" height="12" rx="2" />
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
</svg>
);
};
72 changes: 54 additions & 18 deletions packages/docs/src/repl/repl-output-modules.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,81 @@
import { $, useStore, component$ } from '@builder.io/qwik';
import { CodeBlock } from '../components/code-block/code-block';
import type { ReplModuleOutput } from './types';
import type { PathInView, ReplModuleOutput } from './types';
const FILE_MODULE_DIV_ID = 'file-modules-client-buisness';

export const ReplOutputModules = ({ outputs, headerText }: ReplOutputModulesProps) => {
export const ReplOutputModules = component$(({ outputs, headerText }: ReplOutputModulesProps) => {
const store = useStore<PathInView>({
selectedPath: outputs.length ? outputs[0].path : '',
});
const pathInView$ = $((path: string) => {
store.selectedPath = path;
});
return (
<div class="output-result output-modules">
<div class="file-tree">
<div class="file-tree-header">{outputs.length > 0 ? headerText : ''}</div>
<div class="file-tree-items">
{outputs.map((o, i) => (
<a
href="#"
onClick$={() => {
const fileItem = document.querySelector(`[data-file-item="${i}"]`);
if (fileItem) {
fileItem.scrollIntoView();
}
}}
preventdefault:click
key={o.path}
>
{o.path}
</a>
<div key={o.path}>
<a
href="#"
onClick$={() => {
const fileItem = document.querySelector(`[data-file-item="${i}"]`);
if (fileItem) {
fileItem.scrollIntoView();
}
}}
class={{
'in-view': store.selectedPath && store.selectedPath === o.path,
'!hidden': true,
'md:!block': true,
}}
preventdefault:click
key={o.path}
>
{o.path}
</a>
<div class="block md:hidden">
<div class="file-item" data-file-item={i} key={o.path}>
<div class="file-info">
<span>{o.path}</span>
{o.size ? <span class="file-size">({o.size})</span> : null}
</div>
<div class="file-text">
<CodeBlock
pathInView$={pathInView$}
path={o.path}
code={o.code}
observerRootId={FILE_MODULE_DIV_ID}
/>
</div>
</div>
</div>
</div>
))}
</div>
</div>
<div class="file-modules">
<div class="file-modules" id={FILE_MODULE_DIV_ID}>
{outputs.map((o, i) => (
<div class="file-item" data-file-item={i} key={o.path}>
<div class="file-info">
<span>{o.path}</span>
{o.size ? <span class="file-size">({o.size})</span> : null}
</div>
<div class="file-text">
<CodeBlock path={o.path} code={o.code} />
<CodeBlock
pathInView$={pathInView$}
path={o.path}
code={o.code}
observerRootId={FILE_MODULE_DIV_ID}
/>
</div>
</div>
))}
</div>
</div>
);
};
});

interface ReplOutputModulesProps {
headerText: string;
Expand Down
5 changes: 3 additions & 2 deletions packages/docs/src/repl/repl-output-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { component$ } from '@builder.io/qwik';
import { CodeBlock } from '../components/code-block/code-block';
import { ReplOutputModules } from './repl-output-modules';
import { ReplOutputSymbols } from './repl-output-symbols';
import { ReplTabButton } from './repl-tab-button';
import { ReplTabButtons } from './repl-tab-buttons';
import type { ReplAppInput, ReplStore } from './types';

export const ReplOutputPanel = ({ input, store }: ReplOutputPanelProps) => {
export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProps) => {
const diagnosticsLen = store.diagnostics.length + store.monacoDiagnostics.length;

return (
Expand Down Expand Up @@ -131,7 +132,7 @@ export const ReplOutputPanel = ({ input, store }: ReplOutputPanelProps) => {
</div>
</div>
);
};
});

interface ReplOutputPanelProps {
input: ReplAppInput;
Expand Down
71 changes: 54 additions & 17 deletions packages/docs/src/repl/repl-output-symbols.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,62 @@
import type { TransformModule } from '@builder.io/qwik/optimizer';
import { CodeBlock } from '../components/code-block/code-block';
import { $, useStore, component$ } from '@builder.io/qwik';
import type { PathInView } from './types';
const FILE_MODULE_DIV_ID = 'file-modules-symbol';

export const ReplOutputSymbols = component$(({ outputs }: ReplOutputSymbolsProps) => {
const store = useStore<PathInView>({
selectedPath: outputs.length ? outputs[0].path : '',
});
const pathInView$ = $((path: string) => {
store.selectedPath = path;
});

export const ReplOutputSymbols = ({ outputs }: ReplOutputSymbolsProps) => {
return (
<div class="output-result output-modules">
<div class="file-tree">
<div class="file-tree-header">Symbols</div>
<div class="file-tree-items">
{outputs.map((o, i) => (
<a
href="#"
onClick$={() => {
const fileItem = document.querySelector(`[data-file-item="${i}"]`);
if (fileItem) {
fileItem.scrollIntoView();
}
}}
preventdefault:click
key={o.path}
>
{o.hook?.canonicalFilename}
</a>
<div key={o.path}>
<a
href="#"
onClick$={() => {
const fileItem = document.querySelector(`[data-file-item="${i}"]`);
if (fileItem) {
store.selectedPath = o.path;
fileItem.scrollIntoView();
}
}}
class={{
'in-view': store.selectedPath && store.selectedPath === o.path,
'!hidden': true,
'md:!block': true,
}}
preventdefault:click
>
{o.hook?.canonicalFilename}
</a>
<div class="block md:hidden">
<div class="file-item" data-file-item={i} key={o.path}>
<div class="file-info">
<span>{o.hook?.canonicalFilename}</span>
</div>
<div class="file-text">
<CodeBlock
pathInView$={pathInView$}
path={o.path}
code={o.code}
observerRootId={FILE_MODULE_DIV_ID}
/>
</div>
</div>
</div>
</div>
))}
</div>
</div>
<div class="file-modules">
<div class="file-modules hidden md:block" id={FILE_MODULE_DIV_ID}>
{outputs
.filter((o) => !!o.hook)
.map((o, i) => (
Expand All @@ -33,14 +65,19 @@ export const ReplOutputSymbols = ({ outputs }: ReplOutputSymbolsProps) => {
<span>{o.hook?.canonicalFilename}</span>
</div>
<div class="file-text">
<CodeBlock path={o.path} code={o.code} />
<CodeBlock
pathInView$={pathInView$}
path={o.path}
code={o.code}
observerRootId={FILE_MODULE_DIV_ID}
/>
</div>
</div>
))}
</div>
</div>
);
};
});

interface ReplOutputSymbolsProps {
outputs: TransformModule[];
Expand Down
Loading

0 comments on commit 80ea7cf

Please sign in to comment.