mirror of https://github.com/lukechilds/docs.git
Thomas Osmonson
4 years ago
50 changed files with 1431 additions and 1670 deletions
@ -0,0 +1,51 @@ |
|||
const path = require('path'); |
|||
const remark = require('remark'); |
|||
const flatMap = require('unist-util-flatmap'); |
|||
const { readSync } = require('to-vfile'); |
|||
|
|||
module.exports = function includeMarkdownPlugin({ resolveFrom } = {}) { |
|||
return function transformer(tree, file) { |
|||
return flatMap(tree, node => { |
|||
if (node.type !== 'paragraph') return [node]; |
|||
|
|||
// detect an `@include` statement
|
|||
const includeMatch = |
|||
node.children[0].value && node.children[0].value.match(/^@include\s['"](.*)['"]$/); |
|||
if (!includeMatch) return [node]; |
|||
|
|||
// read the file contents
|
|||
const includePath = path.join(resolveFrom || file.dirname, includeMatch[1]); |
|||
let includeContents; |
|||
try { |
|||
includeContents = readSync(includePath, 'utf8'); |
|||
} catch (err) { |
|||
console.log(err); |
|||
throw new Error( |
|||
`The @include file path at ${includePath} was not found.\n\nInclude Location: ${file.path}:${node.position.start.line}:${node.position.start.column}` |
|||
); |
|||
} |
|||
|
|||
// if we are including a ".md" or ".mdx" file, we add the contents as processed markdown
|
|||
// if any other file type, they are embedded into a code block
|
|||
if (includePath.match(/\.md(?:x)?$/)) { |
|||
// return the file contents in place of the @include
|
|||
// this takes a couple steps because we allow recursive includes
|
|||
const processor = remark().use(includeMarkdownPlugin, { resolveFrom }); |
|||
const ast = processor.parse(includeContents); |
|||
return processor.runSync(ast, includeContents).children; |
|||
} else { |
|||
// trim trailing newline
|
|||
includeContents.contents = includeContents.contents.trim(); |
|||
|
|||
// return contents wrapped inside a "code" node
|
|||
return [ |
|||
{ |
|||
type: 'code', |
|||
lang: includePath.match(/\.(\w+)$/)[1], |
|||
value: includeContents, |
|||
}, |
|||
]; |
|||
} |
|||
}); |
|||
}; |
|||
}; |
@ -0,0 +1,16 @@ |
|||
const memoize = require('micro-memoize'); |
|||
const path = require('path'); |
|||
|
|||
const remarkPlugins = [ |
|||
[require('./remark-include'), { resolveFrom: path.join(__dirname, '../src/includes') }], |
|||
require('remark-vscode'), |
|||
memoize(require('./remark-paragraph-alerts')), |
|||
memoize(require('remark-external-links')), |
|||
memoize(require('remark-emoji')), |
|||
memoize(require('remark-images')), |
|||
memoize(require('remark-unwrap-images')), |
|||
memoize(require('remark-normalize-headings')), |
|||
memoize(require('remark-slug')), |
|||
]; |
|||
|
|||
module.exports = { remarkPlugins }; |
@ -1,37 +0,0 @@ |
|||
const shiki = require('shiki'); |
|||
const visit = require('unist-util-visit'); |
|||
|
|||
module.exports = function shikiPlugin(options) { |
|||
return async function transformer(tree) { |
|||
const theme = (options && options.theme) || 'zeit'; |
|||
let shikiTheme; |
|||
|
|||
try { |
|||
shikiTheme = shiki.getTheme(theme); |
|||
} catch (_) { |
|||
try { |
|||
shikiTheme = shiki.loadTheme(theme); |
|||
} catch (_) { |
|||
throw new Error(`Unable to load theme: ${theme}`); |
|||
} |
|||
} |
|||
|
|||
const highlighter = await shiki.getHighlighter({ |
|||
theme: shikiTheme, |
|||
}); |
|||
|
|||
visit(tree, 'code', (node, _, parent) => { |
|||
node.type = 'html'; |
|||
node.children = undefined; |
|||
if (!node.lang && !options.defaultLang) { |
|||
node.value = `<pre class="shiki-unknown"><code>${node.value}</code></pre>`; |
|||
return; |
|||
} |
|||
|
|||
node.value = highlighter.codeToHtml( |
|||
node.value, |
|||
(node.lang && node.lang.toLowerCase()) || options.defaultLang |
|||
); |
|||
}); |
|||
}; |
|||
}; |
@ -1,17 +1,11 @@ |
|||
import { MDXComponents } from '@components/mdx/mdx-components'; |
|||
import renderToString from 'next-mdx-remote/render-to-string'; |
|||
|
|||
const remarkPlugins = [ |
|||
require('remark-squeeze-paragraphs'), |
|||
require('remark-external-links'), |
|||
require('remark-emoji'), |
|||
require('remark-images'), |
|||
require('remark-unwrap-images'), |
|||
require('remark-normalize-headings'), |
|||
require('remark-slug'), |
|||
]; |
|||
const { remarkPlugins } = require('../../../lib/remark-plugins'); |
|||
|
|||
export const wrapValueInTicks = value => '`' + value.replace('`', '').replace('`', '') + '`'; |
|||
|
|||
export const convertRemoteDataToMDX = async (arr: any[], key: string) => |
|||
Promise.all(arr.map(entry => renderToString(entry[key], MDXComponents, { remarkPlugins }))); |
|||
|
|||
export const renderMdx = async (content: string) => |
|||
renderToString(content, MDXComponents, { remarkPlugins }); |
|||
|
@ -0,0 +1,422 @@ |
|||
/** |
|||
* The state machine used here is based on the one provided |
|||
* in react-native-web: |
|||
* |
|||
* https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/Touchable/index.js
|
|||
*/ |
|||
|
|||
import * as React from 'react'; |
|||
import { isHoverEnabled } from '@common/utils/hover-enabled'; |
|||
import { useGestureResponder } from 'react-gesture-responder'; |
|||
|
|||
/** |
|||
* useTouchable |
|||
* |
|||
* useTouchable is a hook that attempt to emulate native touch behaviour for things |
|||
* like list items, buttons, etc. |
|||
* |
|||
* const { bind, active } = useTouchable({ |
|||
* onPress: () => console.log('hello'), |
|||
* disabled: false, |
|||
* delay: 120 |
|||
* }) |
|||
* |
|||
*/ |
|||
|
|||
const HIGHLIGHT_DELAY_MS = 100; |
|||
const PRESS_EXPAND_PX = 20; |
|||
const LONG_PRESS_DELAY = 500 - HIGHLIGHT_DELAY_MS; |
|||
|
|||
type States = |
|||
| 'ERROR' |
|||
| 'NOT_RESPONDER' |
|||
| 'RESPONDER_ACTIVE_IN' |
|||
| 'RESPONDER_ACTIVE_OUT' |
|||
| 'RESPONDER_PRESSED_IN' |
|||
| 'RESPONDER_PRESSED_OUT' |
|||
| 'RESPONDER_LONG_PRESSED_IN'; |
|||
|
|||
type Events = |
|||
| 'DELAY' |
|||
| 'RESPONDER_GRANT' |
|||
| 'RESPONDER_RELEASE' |
|||
| 'RESPONDER_TERMINATED' |
|||
| 'ENTER_PRESS_RECT' |
|||
| 'LEAVE_PRESS_RECT' |
|||
| 'LONG_PRESS_DETECTED'; |
|||
|
|||
type TransitionsType = { [key in States]: TransitionType }; |
|||
|
|||
type TransitionType = { [key in Events]: States }; |
|||
|
|||
const transitions = { |
|||
NOT_RESPONDER: { |
|||
DELAY: 'NOT_RESPONDER', |
|||
RESPONDER_GRANT: 'RESPONDER_ACTIVE_IN', |
|||
RESPONDER_RELEASE: 'NOT_RESPONDER', |
|||
RESPONDER_TERMINATED: 'NOT_RESPONDER', |
|||
ENTER_PRESS_RECT: 'NOT_RESPONDER', |
|||
LEAVE_PRESS_RECT: 'NOT_RESPONDER', |
|||
LONG_PRESS_DETECTED: 'NOT_RESPONDER', |
|||
}, |
|||
RESPONDER_ACTIVE_IN: { |
|||
DELAY: 'RESPONDER_PRESSED_IN', |
|||
RESPONDER_GRANT: 'ERROR', |
|||
RESPONDER_RELEASE: 'NOT_RESPONDER', |
|||
RESPONDER_TERMINATED: 'NOT_RESPONDER', |
|||
ENTER_PRESS_RECT: 'RESPONDER_ACTIVE_IN', |
|||
LEAVE_PRESS_RECT: 'RESPONDER_ACTIVE_OUT', |
|||
LONG_PRESS_DETECTED: 'ERROR', |
|||
}, |
|||
RESPONDER_ACTIVE_OUT: { |
|||
DELAY: 'RESPONDER_PRESSED_OUT', |
|||
RESPONDER_GRANT: 'ERROR', |
|||
RESPONDER_RELEASE: 'NOT_RESPONDER', |
|||
RESPONDER_TERMINATED: 'NOT_RESPONDER', |
|||
ENTER_PRESS_RECT: 'RESPONDER_ACTIVE_IN', |
|||
LEAVE_PRESS_RECT: 'RESPONDER_ACTIVE_OUT', |
|||
LONG_PRESS_DETECTED: 'ERROR', |
|||
}, |
|||
RESPONDER_PRESSED_IN: { |
|||
DELAY: 'ERROR', |
|||
RESPONDER_GRANT: 'ERROR', |
|||
RESPONDER_RELEASE: 'NOT_RESPONDER', |
|||
RESPONDER_TERMINATED: 'NOT_RESPONDER', |
|||
ENTER_PRESS_RECT: 'RESPONDER_PRESSED_IN', |
|||
LEAVE_PRESS_RECT: 'RESPONDER_PRESSED_OUT', |
|||
LONG_PRESS_DETECTED: 'RESPONDER_LONG_PRESSED_IN', |
|||
}, |
|||
RESPONDER_PRESSED_OUT: { |
|||
DELAY: 'ERROR', |
|||
RESPONDER_GRANT: 'ERROR', |
|||
RESPONDER_RELEASE: 'NOT_RESPONDER', |
|||
RESPONDER_TERMINATED: 'NOT_RESPONDER', |
|||
ENTER_PRESS_RECT: 'RESPONDER_PRESSED_IN', |
|||
LEAVE_PRESS_RECT: 'RESPONDER_PRESSED_OUT', |
|||
LONG_PRESS_DETECTED: 'ERROR', |
|||
}, |
|||
RESPONDER_LONG_PRESSED_IN: { |
|||
DELAY: 'ERROR', |
|||
RESPONDER_GRANT: 'ERROR', |
|||
RESPONDER_RELEASE: 'NOT_RESPONDER', |
|||
RESPONDER_TERMINATED: 'NOT_RESPONDER', |
|||
ENTER_PRESS_RECT: 'RESPONDER_PRESSED_IN', |
|||
LEAVE_PRESS_RECT: 'RESPONDER_PRESSED_OUT', |
|||
LONG_PRESS_DETECTED: 'RESPONDER_LONG_PRESSED_IN', |
|||
}, |
|||
ERROR: { |
|||
DELAY: 'NOT_RESPONDER', |
|||
RESPONDER_GRANT: 'RESPONDER_ACTIVE_IN', |
|||
RESPONDER_RELEASE: 'NOT_RESPONDER', |
|||
RESPONDER_TERMINATED: 'NOT_RESPONDER', |
|||
ENTER_PRESS_RECT: 'NOT_RESPONDER', |
|||
LEAVE_PRESS_RECT: 'NOT_RESPONDER', |
|||
LONG_PRESS_DETECTED: 'NOT_RESPONDER', |
|||
}, |
|||
} as TransitionsType; |
|||
|
|||
export type OnPressFunction = ( |
|||
e?: React.TouchEvent | React.MouseEvent | React.KeyboardEvent | Event |
|||
) => void; |
|||
|
|||
export interface TouchableOptions { |
|||
delay: number; |
|||
longPressDelay: number; |
|||
pressExpandPx: number; |
|||
behavior: 'button' | 'link'; |
|||
disabled: boolean; |
|||
terminateOnScroll: boolean; |
|||
onPress?: OnPressFunction; |
|||
onLongPress?: OnPressFunction; |
|||
} |
|||
|
|||
const defaultOptions: TouchableOptions = { |
|||
delay: HIGHLIGHT_DELAY_MS, |
|||
pressExpandPx: PRESS_EXPAND_PX, |
|||
longPressDelay: LONG_PRESS_DELAY, |
|||
behavior: 'button', |
|||
disabled: false, |
|||
terminateOnScroll: true, |
|||
onPress: undefined, |
|||
onLongPress: undefined, |
|||
}; |
|||
|
|||
export function useTouchable(options: Partial<TouchableOptions> = {}) { |
|||
const { |
|||
onPress, |
|||
onLongPress, |
|||
longPressDelay, |
|||
terminateOnScroll, |
|||
delay, |
|||
behavior, |
|||
disabled: localDisabled, |
|||
} = { |
|||
...defaultOptions, |
|||
...options, |
|||
}; |
|||
const disabled = localDisabled; |
|||
const ref = React.useRef<HTMLAnchorElement | HTMLDivElement | any>(null); |
|||
const delayTimer = React.useRef<number>(); |
|||
const longDelayTimer = React.useRef<number>(); |
|||
const bounds = React.useRef<ClientRect>(); |
|||
const [hover, setHover] = React.useState(false); |
|||
const [showHover, setShowHover] = React.useState(true); |
|||
const [active, setActive] = React.useState(false); |
|||
const state = React.useRef<States>('NOT_RESPONDER'); |
|||
|
|||
/** |
|||
* Transition from one state to another |
|||
* @param event |
|||
*/ |
|||
|
|||
function dispatch(event: Events) { |
|||
const nextState = transitions[state.current][event]; |
|||
state.current = nextState; |
|||
|
|||
if (nextState === 'RESPONDER_PRESSED_IN' || nextState === 'RESPONDER_LONG_PRESSED_IN') { |
|||
setActive(true); |
|||
} else { |
|||
setActive(false); |
|||
} |
|||
|
|||
if (nextState === 'NOT_RESPONDER') { |
|||
clearTimeout(delayTimer.current); |
|||
clearTimeout(longDelayTimer.current); |
|||
} |
|||
} |
|||
|
|||
// create a pan responder to handle mouse / touch gestures
|
|||
const { bind, terminateCurrentResponder } = useGestureResponder({ |
|||
onStartShouldSet: () => true, |
|||
onGrant: () => { |
|||
onStart(isHoverEnabled() ? 0 : undefined); |
|||
}, |
|||
onRelease: (_state, e) => onEnd(e), |
|||
onMove: (_state, e) => onTouchMove(e), |
|||
onTerminate: _state => onTerminate(), |
|||
}); |
|||
|
|||
/** |
|||
* Emit a press event if not disabled |
|||
* @param e |
|||
*/ |
|||
|
|||
function emitPress(e: React.TouchEvent | React.MouseEvent | React.KeyboardEvent | Event) { |
|||
if (!disabled && onPress) { |
|||
onPress(e); |
|||
} |
|||
} |
|||
|
|||
function emitLongPress() { |
|||
if (!disabled && onLongPress) { |
|||
onLongPress(); |
|||
} |
|||
} |
|||
|
|||
function bindScroll() { |
|||
if (terminateOnScroll) { |
|||
document.addEventListener('scroll', onScroll, { |
|||
capture: true, |
|||
passive: true, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
function unbindScroll() { |
|||
document.removeEventListener('scroll', onScroll, true); |
|||
} |
|||
|
|||
function afterDelay() { |
|||
dispatch('DELAY'); |
|||
} |
|||
|
|||
/** |
|||
* Get our initial bounding box clientRect and set any delay |
|||
* timers if necessary. |
|||
* @param delayPressMs |
|||
*/ |
|||
|
|||
function onStart(delayPressMs = delay) { |
|||
dispatch('RESPONDER_GRANT'); |
|||
bounds.current = ref.current?.getBoundingClientRect(); |
|||
delayTimer.current = delayPressMs > 0 ? window.setTimeout(afterDelay, delayPressMs) : undefined; |
|||
|
|||
if (delayPressMs === 0) { |
|||
dispatch('DELAY'); |
|||
} |
|||
|
|||
longDelayTimer.current = window.setTimeout(afterLongDelay, longPressDelay); |
|||
|
|||
bindScroll(); |
|||
setShowHover(false); |
|||
} |
|||
|
|||
function afterLongDelay() { |
|||
dispatch('LONG_PRESS_DETECTED'); |
|||
emitLongPress(); |
|||
} |
|||
|
|||
// onTerminate should be disambiguated from onRelease
|
|||
// because it should never trigger onPress events.
|
|||
function onTerminate() { |
|||
if (state.current === 'NOT_RESPONDER') { |
|||
return; |
|||
} |
|||
|
|||
dispatch('RESPONDER_RELEASE'); |
|||
setShowHover(true); |
|||
unbindScroll(); |
|||
} |
|||
|
|||
function onEnd(e?: React.TouchEvent | React.MouseEvent | React.KeyboardEvent | Event) { |
|||
// consider unbinding the end event instead
|
|||
if (state.current === 'NOT_RESPONDER') { |
|||
return; |
|||
} |
|||
|
|||
if ( |
|||
e && |
|||
(state.current === 'RESPONDER_ACTIVE_IN' || state.current === 'RESPONDER_PRESSED_IN') |
|||
) { |
|||
emitPress(e); |
|||
} |
|||
|
|||
dispatch('RESPONDER_RELEASE'); |
|||
setShowHover(true); |
|||
unbindScroll(); |
|||
} |
|||
|
|||
function isWithinActiveBounds( |
|||
clientX: number, |
|||
clientY: number, |
|||
rect: ClientRect, |
|||
expandPx: number = PRESS_EXPAND_PX |
|||
) { |
|||
return ( |
|||
clientX > rect.left - expandPx && |
|||
clientY > rect.top - expandPx && |
|||
clientX < rect.right + expandPx && |
|||
clientY < rect.bottom + expandPx |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Determine if the touch remains in the active bounds |
|||
* @param e |
|||
*/ |
|||
|
|||
function onTouchMove(e: any) { |
|||
if (state.current === 'NOT_RESPONDER' || state.current === 'ERROR') { |
|||
return; |
|||
} |
|||
|
|||
clearTimeout(longDelayTimer.current); |
|||
|
|||
const { clientX, clientY } = e.touches && e.touches[0] ? e.touches[0] : e; |
|||
const withinBounds = isWithinActiveBounds(clientX, clientY, bounds.current); |
|||
|
|||
if (withinBounds) { |
|||
dispatch('ENTER_PRESS_RECT'); |
|||
} else { |
|||
dispatch('LEAVE_PRESS_RECT'); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Scrolling cancels all responder events. This enables |
|||
* the user to scroll without selecting something |
|||
*/ |
|||
|
|||
function onScroll() { |
|||
unbindScroll(); |
|||
dispatch('RESPONDER_TERMINATED'); |
|||
} |
|||
|
|||
/** |
|||
* If our mouse leaves we terminate our responder, |
|||
* even if our press remains down. This emulates |
|||
* native mouse behaviour. |
|||
* @param e |
|||
*/ |
|||
|
|||
function onMouseLeave() { |
|||
if (hover) { |
|||
setHover(false); |
|||
} |
|||
if (!showHover) { |
|||
setShowHover(true); |
|||
} |
|||
if (state.current !== 'NOT_RESPONDER') { |
|||
terminateCurrentResponder(); |
|||
} |
|||
} |
|||
|
|||
function onMouseEnter() { |
|||
if (!hover) { |
|||
setHover(true); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Handle timer and disabled side-effects |
|||
*/ |
|||
|
|||
React.useEffect(() => { |
|||
return () => { |
|||
clearTimeout(delayTimer.current); |
|||
clearTimeout(longDelayTimer.current); |
|||
unbindScroll(); |
|||
}; |
|||
}, []); |
|||
|
|||
React.useEffect(() => { |
|||
if (disabled && state.current !== 'NOT_RESPONDER') { |
|||
dispatch('RESPONDER_TERMINATED'); |
|||
setShowHover(true); |
|||
} |
|||
}, [disabled]); |
|||
|
|||
/** |
|||
* Keyboard support |
|||
* button: |
|||
* onEnterDown -> onPress |
|||
* onSpaceUp -> onPress |
|||
* Prevent default. |
|||
* |
|||
* link: Don't prevent default |
|||
*/ |
|||
|
|||
function onKey(e: React.KeyboardEvent) { |
|||
const ENTER = 13; |
|||
const SPACE = 32; |
|||
|
|||
if (e.type === 'keydown' && e.which === SPACE) { |
|||
onStart(0); |
|||
} else if (e.type === 'keydown' && e.which === ENTER) { |
|||
emitPress(e); |
|||
} else if (e.type === 'keyup' && e.which === SPACE) { |
|||
onEnd(e); |
|||
} else { |
|||
return; |
|||
} |
|||
|
|||
e.stopPropagation(); |
|||
|
|||
if (!(e.which === ENTER && behavior === 'link')) { |
|||
e.preventDefault(); |
|||
} |
|||
} |
|||
|
|||
return { |
|||
bind: { |
|||
...bind, |
|||
onKeyUp: onKey, |
|||
onKeyDown: onKey, |
|||
onMouseEnter, |
|||
onMouseLeave, |
|||
ref, |
|||
}, |
|||
active: !disabled && active, |
|||
hover: isHoverEnabled() && !disabled && hover && showHover, |
|||
}; |
|||
} |
@ -0,0 +1,43 @@ |
|||
const canUseDOM = !!( |
|||
typeof window !== 'undefined' && |
|||
window.document && |
|||
window.document.createElement |
|||
); |
|||
|
|||
let isEnabled = false; |
|||
|
|||
const HOVER_THRESHOLD_MS = 1000; |
|||
let lastTouchTimestamp = 0; |
|||
|
|||
function enableHover() { |
|||
if (isEnabled || Date.now() - lastTouchTimestamp < HOVER_THRESHOLD_MS) { |
|||
return; |
|||
} |
|||
isEnabled = true; |
|||
} |
|||
|
|||
function disableHover() { |
|||
lastTouchTimestamp = Date.now(); |
|||
if (isEnabled) { |
|||
isEnabled = false; |
|||
} |
|||
} |
|||
|
|||
if (canUseDOM) { |
|||
document.addEventListener('touchstart', disableHover, { |
|||
capture: true, |
|||
passive: true, |
|||
}); |
|||
document.addEventListener('touchmove', disableHover, { |
|||
capture: true, |
|||
passive: true, |
|||
}); |
|||
document.addEventListener('mousemove', enableHover, { |
|||
capture: true, |
|||
passive: true, |
|||
}); |
|||
} |
|||
|
|||
export function isHoverEnabled() { |
|||
return isEnabled; |
|||
} |
@ -1,91 +1,19 @@ |
|||
import React from 'react'; |
|||
import { MDXComponents } from '@components/mdx/mdx-components'; |
|||
import { Box } from '@blockstack/ui'; |
|||
import { TableOfContents } from '@components/toc'; |
|||
import { hydrate } from '@common/hydrate-mdx'; |
|||
|
|||
const renderFunctionsSection = entry => ( |
|||
<> |
|||
<MDXComponents.h3>{entry.name}</MDXComponents.h3> |
|||
|
|||
<MDXComponents.p> |
|||
<strong>Signature:</strong>{' '} |
|||
<MDXComponents.inlineCode>{entry.signature}</MDXComponents.inlineCode> |
|||
</MDXComponents.p> |
|||
|
|||
<MDXComponents.p> |
|||
<strong>Input:</strong>{' '} |
|||
<MDXComponents.inlineCode>{entry.input_type}</MDXComponents.inlineCode> |
|||
</MDXComponents.p> |
|||
|
|||
<MDXComponents.p> |
|||
<strong>Output:</strong>{' '} |
|||
<MDXComponents.inlineCode>{entry.output_type}</MDXComponents.inlineCode> |
|||
</MDXComponents.p> |
|||
|
|||
{hydrate(entry.description, { |
|||
...MDXComponents, |
|||
p: (props: any) => ( |
|||
<MDXComponents.p |
|||
{...props} |
|||
style={{ display: 'block', wordBreak: 'break-word', hyphens: 'auto' }} |
|||
/> |
|||
), |
|||
})} |
|||
|
|||
<MDXComponents.h4>Example</MDXComponents.h4> |
|||
|
|||
{/* @ts-ignore */} |
|||
<MDXComponents.code>{entry.example}</MDXComponents.code> |
|||
</> |
|||
); |
|||
|
|||
const renderKeywordsSection = entry => ( |
|||
<> |
|||
<MDXComponents.h3>{entry.name}</MDXComponents.h3> |
|||
|
|||
<MDXComponents.p> |
|||
<strong>Output:</strong>{' '} |
|||
<MDXComponents.inlineCode>{entry.output_type}</MDXComponents.inlineCode> |
|||
</MDXComponents.p> |
|||
|
|||
{hydrate(entry.description, MDXComponents)} |
|||
|
|||
<MDXComponents.h4>Example</MDXComponents.h4> |
|||
|
|||
{/* @ts-ignore */} |
|||
<MDXComponents.code>{entry.example}</MDXComponents.code> |
|||
</> |
|||
); |
|||
|
|||
export const ClarityKeywordReference = ({ entries }) => { |
|||
export const ClarityKeywordReference = ({ content, headings }) => { |
|||
return ( |
|||
<> |
|||
<Box> |
|||
<TableOfContents |
|||
label="Contents" |
|||
headings={entries.map(entry => ({ |
|||
content: entry.name, |
|||
level: 1, |
|||
}))} |
|||
/> |
|||
</Box> |
|||
{entries.map(renderKeywordsSection)} |
|||
<TableOfContents label="Contents" headings={headings} /> |
|||
{hydrate(content, MDXComponents)} |
|||
</> |
|||
); |
|||
}; |
|||
export const ClarityFunctionReference = ({ entries }) => ( |
|||
export const ClarityFunctionReference = ({ content, headings }) => ( |
|||
<> |
|||
<Box> |
|||
<TableOfContents |
|||
columns={[2, 2, 3]} |
|||
label="Contents" |
|||
headings={entries.map(entry => ({ |
|||
content: entry.name, |
|||
level: 1, |
|||
}))} |
|||
/> |
|||
</Box> |
|||
{entries.map(renderFunctionsSection)} |
|||
<TableOfContents columns={[2, 2, 3]} label="Contents" headings={headings} /> |
|||
{hydrate(content, MDXComponents)} |
|||
</> |
|||
); |
|||
|
@ -1,98 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Highlighter, HighlighterProps } from '../highlighter'; |
|||
import { Box, BoxProps, color } from '@blockstack/ui'; |
|||
import { css } from '@styled-system/css'; |
|||
|
|||
// Languages used in docs
|
|||
// when adding a new language in the docs, import the theme here
|
|||
import 'prismjs/components/prism-bash'; |
|||
import 'prismjs/components/prism-css'; |
|||
import 'prismjs/components/prism-jsx'; |
|||
import 'prismjs/components/prism-tsx'; |
|||
import 'prismjs/components/prism-json'; |
|||
import 'prismjs/components/prism-toml'; |
|||
import 'prismjs/components/prism-python'; |
|||
import 'prismjs/components/prism-kotlin'; |
|||
|
|||
interface CodeBlock { |
|||
live?: boolean; |
|||
showLineNumbers?: boolean; |
|||
highlight?: string; |
|||
} |
|||
|
|||
export type CodeBlockProps = CodeBlock & HighlighterProps & BoxProps; |
|||
|
|||
const getHighlightLineNumbers = str => |
|||
str && |
|||
str |
|||
.split(' ') |
|||
.join('') |
|||
.split(',') |
|||
.flatMap(s => { |
|||
if (!s.includes('-')) return +s; |
|||
|
|||
const [min, max] = s.split('-'); |
|||
|
|||
return Array.from({ length: max - min + 1 }, (_, n) => n + +min); |
|||
}); |
|||
|
|||
const CodeBlock = React.memo( |
|||
React.forwardRef( |
|||
( |
|||
{ |
|||
code, |
|||
showLineNumbers, |
|||
hideLineHover, |
|||
style = {}, |
|||
highlightedLines, |
|||
className, |
|||
live = true, |
|||
highlight, |
|||
children, |
|||
...props |
|||
}: CodeBlockProps, |
|||
ref: React.Ref<HTMLDivElement> |
|||
) => { |
|||
const language = className && className.replace(/language-/, ''); |
|||
|
|||
const displayNumbers = showLineNumbers || (language && language !== 'bash'); |
|||
|
|||
return ( |
|||
<Box |
|||
className={displayNumbers ? 'line-numbers' : ''} |
|||
bg="ink" |
|||
border="1px solid" |
|||
borderColor={color('border')} |
|||
borderRightWidth={['0px', '0px', '1px']} |
|||
borderLeftWidth={['0px', '0px', '1px']} |
|||
borderRadius={[0, 0, '12px']} |
|||
overflow="hidden" |
|||
> |
|||
<Box |
|||
ref={ref} |
|||
css={css({ |
|||
...style, |
|||
// @ts-ignore
|
|||
overflowX: 'auto', |
|||
// @ts-ignore
|
|||
color: 'white', |
|||
// @ts-ignore
|
|||
whiteSpace: 'pre', |
|||
...props, |
|||
})} |
|||
> |
|||
<Highlighter |
|||
language={language as any} |
|||
code={children.toString().trim()} |
|||
showLineNumbers={displayNumbers} |
|||
highlightedLines={getHighlightLineNumbers(highlight)} |
|||
hideLineHover |
|||
/> |
|||
</Box> |
|||
</Box> |
|||
); |
|||
} |
|||
) |
|||
); |
|||
|
|||
export default CodeBlock; |
@ -1,100 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import Editor from 'react-simple-code-editor'; |
|||
import { createGlobalStyle } from 'styled-components'; |
|||
import { Box, BoxProps, Highlighter } from '@blockstack/ui'; |
|||
// TODO: change when new version is published
|
|||
import { Language } from '@blockstack/ui/dist/ui/src/highlighter/types'; |
|||
|
|||
const TextAreaOverrides = createGlobalStyle` |
|||
.code-editor{ |
|||
input, |
|||
textarea, |
|||
[contenteditable] { |
|||
caret-color: white; |
|||
} |
|||
& * { |
|||
font-size: 14px !important; |
|||
} |
|||
textarea{ |
|||
width: 100% !important; |
|||
padding-left: 16px !important; |
|||
font-size: 14px !important; |
|||
padding-top: 1px !important; |
|||
font-family: 'Fira Code',monospace !important; |
|||
line-height: 24px !important; |
|||
outline: transparent; |
|||
} |
|||
& > div{ |
|||
overflow: initial !important; |
|||
} |
|||
textarea, pre { |
|||
white-space: pre !important; |
|||
overflow-wrap: unset !important; |
|||
} |
|||
} |
|||
`;
|
|||
|
|||
interface CodeEditorProps extends Partial<Omit<BoxProps, 'onChange'>> { |
|||
value: string; |
|||
disabled?: boolean; |
|||
language?: Language; |
|||
onChange?: (code: string) => void; |
|||
name?: string; |
|||
id?: string; |
|||
} |
|||
|
|||
export const CodeEditor = React.memo((props: CodeEditorProps) => { |
|||
const { style, value, onChange, language, id, disabled, maxHeight, ...rest } = props; |
|||
const [code, setState] = React.useState(value); |
|||
|
|||
const updateContent = (c: string) => { |
|||
if (c === code) { |
|||
return; |
|||
} |
|||
setState(s => { |
|||
if (props.onChange) { |
|||
props.onChange(c); |
|||
} |
|||
return c; |
|||
}); |
|||
}; |
|||
|
|||
React.useEffect(() => { |
|||
if (value !== code) { |
|||
updateContent(value); |
|||
} |
|||
}, [value]); |
|||
|
|||
return ( |
|||
<> |
|||
<TextAreaOverrides /> |
|||
<Box |
|||
className="code-editor" |
|||
bg="ink" |
|||
borderRadius={['unset', 'unset', '12px', '12px']} |
|||
py="base-tight" |
|||
border="1px solid var(--colors-border)" |
|||
overflowX="auto" |
|||
minWidth="100%" |
|||
maxHeight={maxHeight} |
|||
> |
|||
<Editor |
|||
textareaId={id} |
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
|||
// @ts-ignore
|
|||
language={language} |
|||
onValueChange={updateContent} |
|||
highlight={c => <Highlighter code={c} language={language} />} |
|||
style={{ |
|||
...style, |
|||
overflowWrap: 'unset', |
|||
whiteSpace: 'pre !important' as any, |
|||
}} |
|||
value={code} |
|||
{...rest} |
|||
/> |
|||
</Box> |
|||
</> |
|||
); |
|||
}); |
@ -1,232 +0,0 @@ |
|||
import React from 'react'; |
|||
import Highlight from 'prism-react-renderer'; |
|||
import { Box, Flex, space, useTheme } from '@blockstack/ui'; |
|||
import { GrammaticalToken, GetGrammaticalTokenProps, RenderProps, Language } from './types'; |
|||
import Prism from 'prism-react-renderer/prism'; |
|||
import { theme } from '@components/highlighter/prism-theme'; |
|||
import './language-definition'; |
|||
import { css } from '@styled-system/css'; |
|||
|
|||
const startPad = (n: number, z = 2, s = '0') => |
|||
(n + '').length <= z ? ['', '-'][+(n < 0)] + (s.repeat(z) + Math.abs(n)).slice(-1 * z) : n + ''; |
|||
|
|||
const LINE_NUMBER_WIDTH = 50; |
|||
const getLineNumber = (n: number, length: number) => startPad(n + 1, length.toString().length); |
|||
|
|||
const Tokens = ({ |
|||
tokens, |
|||
getTokenProps, |
|||
showLineNumbers, |
|||
...rest |
|||
}: { |
|||
tokens: GrammaticalToken[]; |
|||
getTokenProps: GetGrammaticalTokenProps; |
|||
showLineNumbers?: boolean; |
|||
}) => { |
|||
const bsTheme = useTheme(); |
|||
const pl = showLineNumbers |
|||
? [`calc(${LINE_NUMBER_WIDTH}px + ${(bsTheme as any).sizes['base']})`] |
|||
: ['unset', 'unset', 'base', 'base']; |
|||
|
|||
return ( |
|||
<Box pl={pl} pr="base" position="relative" zIndex={2} {...rest}> |
|||
{tokens.map( |
|||
(token, key) => |
|||
token.content !== '// highlight' && ( |
|||
<Box py="2px" display="inline-block" {...getTokenProps({ token, key })} /> |
|||
) |
|||
)} |
|||
</Box> |
|||
); |
|||
}; |
|||
const LineNumber = ({ number, length, ...rest }: { number: number; length: number }) => ( |
|||
<Flex |
|||
textAlign="right" |
|||
pr={space('tight')} |
|||
pl={space('tight')} |
|||
width={LINE_NUMBER_WIDTH} |
|||
borderRight="1px solid" |
|||
borderRightColor="inherit" |
|||
color="ink.400" |
|||
flexShrink={0} |
|||
style={{ userSelect: 'none' }} |
|||
position="absolute" |
|||
left={0} |
|||
height="100%" |
|||
align="baseline" |
|||
justify="center" |
|||
zIndex={1} |
|||
css={css({ |
|||
color: 'rgba(255,255,255,0.6)', |
|||
whiteSpace: 'pre', |
|||
fontFamily: 'Fira Code, Consolata, monospace', |
|||
fontSize: '14.556040756914118px', |
|||
lineHeight: '24px', |
|||
padding: '2px 0', |
|||
'::before': { |
|||
content: "''", |
|||
marginTop: '-0.47483499999999995em', |
|||
display: 'block', |
|||
height: 0, |
|||
}, |
|||
'::after': { |
|||
content: "''", |
|||
marginBottom: '-0.493835em', |
|||
display: 'block', |
|||
height: 0, |
|||
}, |
|||
})} |
|||
{...rest} |
|||
> |
|||
{getLineNumber(number, length)} |
|||
</Flex> |
|||
); |
|||
|
|||
const Line = ({ |
|||
tokens, |
|||
getTokenProps, |
|||
index, |
|||
length, |
|||
showLineNumbers, |
|||
hideLineHover, |
|||
highlighted, |
|||
...rest |
|||
}: { |
|||
tokens: GrammaticalToken[]; |
|||
index: number; |
|||
length: number; |
|||
getTokenProps: GetGrammaticalTokenProps; |
|||
showLineNumbers?: boolean; |
|||
hideLineHover?: boolean; |
|||
highlighted?: boolean; |
|||
}) => { |
|||
const highlightedStyle = { |
|||
bg: ['unset', 'unset', 'ink.900'], |
|||
borderColor: ['ink.900', 'ink.900', 'ink.600'], |
|||
}; |
|||
const hasHighlightComment = !!tokens.find(token => token.content === '// highlight'); |
|||
const isHighlighted = highlighted || hasHighlightComment; |
|||
const highlighedProps = isHighlighted ? highlightedStyle : {}; |
|||
|
|||
return ( |
|||
<Flex |
|||
height="loose" |
|||
align="baseline" |
|||
borderColor="ink.900" |
|||
_hover={hideLineHover ? undefined : highlightedStyle} |
|||
position="relative" |
|||
{...highlighedProps} |
|||
{...rest} |
|||
> |
|||
{showLineNumbers ? <LineNumber number={index} length={length} /> : null} |
|||
<Tokens showLineNumbers={showLineNumbers} getTokenProps={getTokenProps} tokens={tokens} /> |
|||
</Flex> |
|||
); |
|||
}; |
|||
|
|||
const Spacer = ({ showLineNumbers }: { showLineNumbers?: boolean }) => ( |
|||
<Flex height="base-loose" bg="ink" width="100%"> |
|||
{showLineNumbers && ( |
|||
<Box |
|||
height="base-loose" |
|||
borderRight="1px solid" |
|||
borderRightColor="ink.900" |
|||
width={`${LINE_NUMBER_WIDTH}px`} |
|||
/> |
|||
)} |
|||
</Flex> |
|||
); |
|||
const Lines = ({ |
|||
tokens: lines, |
|||
getLineProps, |
|||
getTokenProps, |
|||
className, |
|||
showLineNumbers, |
|||
hideLineHover, |
|||
highlightedLines, |
|||
}: { |
|||
showLineNumbers?: boolean; |
|||
hideLineHover?: boolean; |
|||
highlightedLines?: number[]; |
|||
} & RenderProps) => { |
|||
const displayNumbers = lines?.length > 2 && showLineNumbers; |
|||
return ( |
|||
<Box display="block" className={className}> |
|||
<Box display="block"> |
|||
<Spacer showLineNumbers={displayNumbers} /> |
|||
{lines.map((tokens, i) => ( |
|||
<Box |
|||
css={css({ |
|||
'& > *': { |
|||
fontFamily: 'Fira Code, Consolata, monospace', |
|||
fontSize: '14.556040756914118px', |
|||
lineHeight: '24px', |
|||
padding: '0.05px 0', |
|||
'::before': { |
|||
content: "''", |
|||
marginTop: '-0.47483499999999995em', |
|||
display: 'block', |
|||
height: 0, |
|||
}, |
|||
'::after': { |
|||
content: "''", |
|||
marginBottom: '-0.493835em', |
|||
display: 'block', |
|||
height: 0, |
|||
}, |
|||
}, |
|||
})} |
|||
> |
|||
<Line |
|||
index={i} |
|||
tokens={tokens} |
|||
getTokenProps={getTokenProps} |
|||
length={lines.length + 1} |
|||
showLineNumbers={displayNumbers} |
|||
highlighted={ |
|||
highlightedLines?.length && |
|||
!!highlightedLines.find(lineNumber => lineNumber === i + 1) |
|||
} |
|||
hideLineHover={hideLineHover || lines.length < 3} |
|||
{...getLineProps({ line: tokens, key: i })} |
|||
/> |
|||
</Box> |
|||
))} |
|||
<Spacer showLineNumbers={displayNumbers} /> |
|||
</Box> |
|||
</Box> |
|||
); |
|||
}; |
|||
|
|||
export interface HighlighterProps { |
|||
code: string; |
|||
language?: Language; |
|||
showLineNumbers?: boolean; |
|||
hideLineHover?: boolean; |
|||
highlightedLines?: number[]; |
|||
} |
|||
|
|||
export const Highlighter = React.memo( |
|||
({ |
|||
code, |
|||
language = 'clarity', |
|||
showLineNumbers, |
|||
hideLineHover, |
|||
highlightedLines, |
|||
}: HighlighterProps) => { |
|||
return ( |
|||
<Highlight Prism={Prism} theme={theme} code={code} language={language as any}> |
|||
{props => ( |
|||
<Lines |
|||
showLineNumbers={showLineNumbers} |
|||
highlightedLines={highlightedLines} |
|||
hideLineHover={hideLineHover} |
|||
{...props} |
|||
/> |
|||
)} |
|||
</Highlight> |
|||
); |
|||
} |
|||
); |
|||
|
|||
Highlighter.displayName = 'Highlighter'; |
@ -1,117 +0,0 @@ |
|||
// @ts-nocheck
|
|||
import Prism from 'prism-react-renderer/prism'; |
|||
|
|||
(function (Prism) { |
|||
// Functions to construct regular expressions
|
|||
// simple form
|
|||
// e.g. (interactive ... or (interactive)
|
|||
function simple_form(name) { |
|||
return RegExp('(\\()' + name + '(?=[\\s\\)])'); |
|||
} |
|||
// booleans and numbers
|
|||
function primitive(pattern) { |
|||
return RegExp('([\\s([])' + pattern + '(?=[\\s)])'); |
|||
} |
|||
|
|||
// Patterns in regular expressions
|
|||
|
|||
// Open parenthesis for look-behind
|
|||
const par = '(\\()'; |
|||
const endpar = '(?=\\))'; |
|||
// End the pattern with look-ahead space
|
|||
const space = '(?=\\s)'; |
|||
|
|||
const language = { |
|||
// Three or four semicolons are considered a heading.
|
|||
heading: { |
|||
pattern: /;;;.*/, |
|||
alias: ['comment', 'title'], |
|||
}, |
|||
comment: /;;.*/, |
|||
string: [ |
|||
{ |
|||
pattern: /"(?:[^"\\]|\\.)*"/, |
|||
greedy: true, |
|||
}, |
|||
{ |
|||
pattern: /0x[0-9a-fA-F]*/, |
|||
greedy: true, |
|||
}, |
|||
], |
|||
symbol: { |
|||
pattern: /'[^()#'\s]+/, |
|||
greedy: true, |
|||
}, |
|||
keyword: [ |
|||
{ |
|||
pattern: RegExp( |
|||
par + |
|||
'(?:or|and|xor|not|begin|let|if|ok|err|unwrap\\!|unwrap-err\\!|unwrap-panic|unwrap-err-panic|match|try\\!|asserts\\!|\ |
|||
map-get\\?|var-get|contract-map-get\\?|get|tuple|\ |
|||
define-public|define-private|define-constant|define-map|define-data-var|\ |
|||
define-fungible-token|define-non-fungible-token|\ |
|||
define-read-only)' + |
|||
space |
|||
), |
|||
lookbehind: true, |
|||
}, |
|||
{ |
|||
pattern: RegExp(par + '(?:is-eq|is-some|is-none|is-ok|is-er)' + space), |
|||
lookbehind: true, |
|||
}, |
|||
{ |
|||
pattern: RegExp( |
|||
par + |
|||
'(?:var-set|map-set|map-delete|map-insert|\ |
|||
ft-transfer\\?|nft-transfer\\?|nft-mint\\?|ft-mint\\?|nft-get-owner\\?|ft-get-balance\\?|\ |
|||
contract-call\\?)' + |
|||
space |
|||
), |
|||
lookbehind: true, |
|||
}, |
|||
{ |
|||
pattern: RegExp( |
|||
par + |
|||
'(?:list|map|filter|fold|len|concat|append|as-max-len\\?|to-int|to-uint|\ |
|||
buff|hash160|sha256|sha512|sha512/256|keccak256|true|false|none)' + |
|||
space |
|||
), |
|||
lookbehind: true, |
|||
}, |
|||
{ |
|||
pattern: RegExp( |
|||
par + |
|||
'(?:as-contract|contract-caller|tx-sender|block-height|at-block|get-block-info\\?)' + |
|||
space |
|||
), |
|||
lookbehind: true, |
|||
}, |
|||
{ |
|||
pattern: RegExp(par + '(?:is-eq|is-some|is-none|is-ok|is-err)' + space), |
|||
lookbehind: true, |
|||
}, |
|||
], |
|||
boolean: /(?:false|true|none)/, |
|||
number: { |
|||
pattern: primitive('[-]?u?\\d+'), |
|||
lookbehind: true, |
|||
}, |
|||
address: { |
|||
pattern: /([\s()])(?:\'[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41})(?=[()\s]|$)/, |
|||
lookbehind: true, |
|||
}, |
|||
operator: { |
|||
pattern: /(\()(?:[-+*\/]|[<>]=?|=>?)(?=[()\s]|$)/, |
|||
lookbehind: true, |
|||
}, |
|||
function: { |
|||
pattern: /(\()[^()'\s]+(?=[()\s]|$)/, |
|||
lookbehind: true, |
|||
}, |
|||
punctuation: /[()']/, |
|||
}; |
|||
|
|||
if (Prism && Prism.languages) { |
|||
Prism.languages.clarity = language; |
|||
} |
|||
})(Prism); |
@ -1,205 +0,0 @@ |
|||
code[class*="language-"], |
|||
pre[class*="language-"] { |
|||
text-align: left; |
|||
white-space: pre; |
|||
word-spacing: normal; |
|||
word-break: normal; |
|||
word-wrap: normal; |
|||
color: #eee; |
|||
background: #2f2f2f; |
|||
font-family: Roboto Mono, monospace; |
|||
font-size: 1em; |
|||
line-height: 1.5em; |
|||
|
|||
-moz-tab-size: 4; |
|||
-o-tab-size: 4; |
|||
tab-size: 4; |
|||
|
|||
-webkit-hyphens: none; |
|||
-moz-hyphens: none; |
|||
-ms-hyphens: none; |
|||
hyphens: none; |
|||
} |
|||
|
|||
code[class*="language-"]::-moz-selection, |
|||
pre[class*="language-"]::-moz-selection, |
|||
code[class*="language-"] ::-moz-selection, |
|||
pre[class*="language-"] ::-moz-selection { |
|||
background: #363636; |
|||
} |
|||
|
|||
code[class*="language-"]::selection, |
|||
pre[class*="language-"]::selection, |
|||
code[class*="language-"] ::selection, |
|||
pre[class*="language-"] ::selection { |
|||
background: #363636; |
|||
} |
|||
|
|||
:not(pre) > code[class*="language-"] { |
|||
white-space: normal; |
|||
border-radius: 0.2em; |
|||
padding: 0.1em; |
|||
} |
|||
|
|||
pre[class*="language-"] { |
|||
overflow: auto; |
|||
position: relative; |
|||
margin: 0.5em 0; |
|||
padding: 1.25em 1em; |
|||
} |
|||
|
|||
.language-css > code, |
|||
.language-sass > code, |
|||
.language-scss > code { |
|||
color: #fd9170; |
|||
} |
|||
|
|||
[class*="language-"] .namespace { |
|||
opacity: 0.7; |
|||
} |
|||
|
|||
.token.atrule { |
|||
color: #c792ea; |
|||
} |
|||
|
|||
.token.attr-name { |
|||
color: #ffcb6b; |
|||
} |
|||
|
|||
.token.attr-value { |
|||
color: #a5e844; |
|||
} |
|||
|
|||
.token.attribute { |
|||
color: #a5e844; |
|||
} |
|||
|
|||
.token.boolean { |
|||
color: #c792ea; |
|||
} |
|||
|
|||
.token.builtin { |
|||
color: #ffcb6b; |
|||
} |
|||
|
|||
.token.cdata { |
|||
color: #80cbc4; |
|||
} |
|||
|
|||
.token.char { |
|||
color: #80cbc4; |
|||
} |
|||
|
|||
.token.class { |
|||
color: #ffcb6b; |
|||
} |
|||
|
|||
.token.class-name { |
|||
color: #f2ff00; |
|||
} |
|||
|
|||
.token.comment { |
|||
color: #616161; |
|||
} |
|||
|
|||
.token.constant { |
|||
color: #c792ea; |
|||
} |
|||
|
|||
.token.deleted { |
|||
color: #ff6666; |
|||
} |
|||
|
|||
.token.doctype { |
|||
color: #616161; |
|||
} |
|||
|
|||
.token.entity { |
|||
color: #ff6666; |
|||
} |
|||
|
|||
.token.function { |
|||
color: #c792ea; |
|||
} |
|||
|
|||
.token.hexcode { |
|||
color: #f2ff00; |
|||
} |
|||
|
|||
.token.id { |
|||
color: #c792ea; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.token.important { |
|||
color: #c792ea; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.token.inserted { |
|||
color: #80cbc4; |
|||
} |
|||
|
|||
.token.keyword { |
|||
color: #c792ea; |
|||
} |
|||
|
|||
.token.number { |
|||
color: #fd9170; |
|||
} |
|||
|
|||
.token.operator { |
|||
color: #89ddff; |
|||
} |
|||
|
|||
.token.prolog { |
|||
color: #616161; |
|||
} |
|||
|
|||
.token.property { |
|||
color: #80cbc4; |
|||
} |
|||
|
|||
.token.pseudo-class { |
|||
color: #a5e844; |
|||
} |
|||
|
|||
.token.pseudo-element { |
|||
color: #a5e844; |
|||
} |
|||
|
|||
.token.punctuation { |
|||
color: #89ddff; |
|||
} |
|||
|
|||
.token.regex { |
|||
color: #f2ff00; |
|||
} |
|||
|
|||
.token.selector { |
|||
color: #ff6666; |
|||
} |
|||
|
|||
.token.string { |
|||
color: #a5e844; |
|||
} |
|||
|
|||
.token.symbol { |
|||
color: #c792ea; |
|||
} |
|||
|
|||
.token.tag { |
|||
color: #ff6666; |
|||
} |
|||
|
|||
.token.unit { |
|||
color: #fd9170; |
|||
} |
|||
|
|||
.token.url { |
|||
color: #ff6666; |
|||
} |
|||
|
|||
.token.variable { |
|||
color: #ff6666; |
|||
} |
@ -1,140 +0,0 @@ |
|||
import { PrismTheme } from 'prism-react-renderer'; |
|||
|
|||
export const theme: PrismTheme = { |
|||
plain: { |
|||
color: 'rgba(255,255,255,1)', |
|||
backgroundColor: '#0f111a', |
|||
}, |
|||
styles: [ |
|||
{ |
|||
types: ['string'], |
|||
style: { |
|||
color: 'rgb(195, 232, 141)', |
|||
}, |
|||
}, |
|||
{ |
|||
types: ['boolean'], |
|||
style: { |
|||
color: 'rgb(255, 156, 172)', |
|||
}, |
|||
}, |
|||
{ |
|||
types: ['number', 'keyword', 'operator'], |
|||
style: { |
|||
color: 'rgb(247, 140, 108)', |
|||
}, |
|||
}, |
|||
{ |
|||
types: ['comment'], |
|||
style: { |
|||
color: 'rgba(255,255,255,0.6)', |
|||
fontStyle: 'italic', |
|||
}, |
|||
}, |
|||
{ |
|||
types: ['punctuation', 'builtin'], |
|||
style: { |
|||
color: 'rgb(137, 221, 255)', |
|||
}, |
|||
}, |
|||
{ |
|||
types: ['tag'], |
|||
style: { |
|||
color: 'rgb(240, 113, 120)', |
|||
}, |
|||
}, |
|||
{ |
|||
types: ['attr-name'], |
|||
style: { |
|||
color: 'rgb(255, 203, 107)', |
|||
}, |
|||
}, |
|||
{ |
|||
types: ['function'], |
|||
style: { |
|||
color: 'rgb(130, 170, 255)', |
|||
}, |
|||
}, |
|||
{ |
|||
types: ['constant'], |
|||
style: { |
|||
color: 'rgb(137, 221, 255)', |
|||
fontStyle: 'italic', |
|||
}, |
|||
}, |
|||
], |
|||
}; |
|||
// export const theme: PrismTheme = {
|
|||
// plain: {
|
|||
// color: '#fff',
|
|||
// backgroundColor: 'transparent',
|
|||
// },
|
|||
// styles: [
|
|||
// {
|
|||
// types: ['prolog'],
|
|||
// style: {
|
|||
// color: 'rgb(0, 0, 128)',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['comment', 'punctuation'],
|
|||
// style: {
|
|||
// color: 'rgb(106, 153, 85)',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['builtin', 'tag', 'changed', 'function', 'keyword'],
|
|||
// style: {
|
|||
// color: 'rgb(86, 156, 214)',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['number', 'variable', 'inserted'],
|
|||
// style: {
|
|||
// color: '#A58FFF',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['operator'],
|
|||
// style: {
|
|||
// color: 'rgb(212, 212, 212)',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['constant'],
|
|||
// style: {
|
|||
// color: 'rgb(100, 102, 149)',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['attr-name'],
|
|||
// style: {
|
|||
// color: 'rgb(156, 220, 254)',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['car'],
|
|||
// style: {
|
|||
// color: 'rgb(156, 220, 254)',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['deleted', 'string'],
|
|||
// style: {
|
|||
// color: '#FF7B48',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['class-name'],
|
|||
// style: {
|
|||
// color: 'rgb(78, 201, 176)',
|
|||
// },
|
|||
// },
|
|||
// {
|
|||
// types: ['char'],
|
|||
// style: {
|
|||
// color: '#FF7B48',
|
|||
// },
|
|||
// },
|
|||
// ],
|
|||
// };
|
@ -1,90 +0,0 @@ |
|||
import * as React from 'react'; |
|||
|
|||
export interface GrammaticalToken { |
|||
types: string[]; |
|||
content: string; |
|||
empty?: boolean; |
|||
} |
|||
|
|||
export interface StyleObj { |
|||
[key: string]: string | number | null; |
|||
} |
|||
|
|||
export interface GrammaticalTokenOutputProps { |
|||
key?: React.Key; |
|||
style?: StyleObj; |
|||
className: string; |
|||
children: string; |
|||
[otherProp: string]: any; |
|||
} |
|||
|
|||
export interface GrammaticalTokenInputProps { |
|||
key?: React.Key; |
|||
style?: StyleObj; |
|||
className?: string; |
|||
token: GrammaticalToken; |
|||
[otherProp: string]: any; |
|||
} |
|||
|
|||
export interface LineInputProps { |
|||
key?: React.Key; |
|||
style?: StyleObj; |
|||
className?: string; |
|||
line: GrammaticalToken[]; |
|||
[otherProp: string]: any; |
|||
} |
|||
|
|||
export interface LineOutputProps { |
|||
key?: React.Key; |
|||
style?: StyleObj; |
|||
className: string; |
|||
[otherProps: string]: any; |
|||
} |
|||
|
|||
export interface RenderProps { |
|||
tokens: GrammaticalToken[][]; |
|||
className: string; |
|||
style: StyleObj; |
|||
getLineProps: (input: LineInputProps) => LineOutputProps; |
|||
getTokenProps: (input: GrammaticalTokenInputProps) => GrammaticalTokenOutputProps; |
|||
} |
|||
|
|||
export type GetGrammaticalTokenProps = ( |
|||
input: GrammaticalTokenInputProps |
|||
) => GrammaticalTokenOutputProps; |
|||
|
|||
export type Language = |
|||
| 'markup' |
|||
| 'bash' |
|||
| 'clarity' |
|||
| 'clike' |
|||
| 'c' |
|||
| 'cpp' |
|||
| 'css' |
|||
| 'javascript' |
|||
| 'jsx' |
|||
| 'coffeescript' |
|||
| 'actionscript' |
|||
| 'css-extr' |
|||
| 'diff' |
|||
| 'git' |
|||
| 'go' |
|||
| 'graphql' |
|||
| 'handlebars' |
|||
| 'json' |
|||
| 'less' |
|||
| 'lisp' |
|||
| 'makefile' |
|||
| 'markdown' |
|||
| 'objectivec' |
|||
| 'ocaml' |
|||
| 'python' |
|||
| 'reason' |
|||
| 'sass' |
|||
| 'scss' |
|||
| 'sql' |
|||
| 'stylus' |
|||
| 'tsx' |
|||
| 'typescript' |
|||
| 'wasm' |
|||
| 'yaml'; |
@ -1,383 +0,0 @@ |
|||
import { |
|||
Box, |
|||
Flex, |
|||
FlexProps, |
|||
BoxProps, |
|||
color, |
|||
themeColor, |
|||
useClipboard, |
|||
space, |
|||
} from '@blockstack/ui'; |
|||
import NextLink from 'next/link'; |
|||
import React, { forwardRef, Ref } from 'react'; |
|||
import LinkIcon from 'mdi-react/LinkVariantIcon'; |
|||
import HashtagIcon from 'mdi-react/HashtagIcon'; |
|||
import { useHover } from 'use-events'; |
|||
import { Tooltip } from '@components/tooltip'; |
|||
import { useActiveHeading } from '@common/hooks/use-active-heading'; |
|||
import { Text, Title } from '@components/typography'; |
|||
import { border } from '@common/utils'; |
|||
import { css } from '@styled-system/css'; |
|||
import { getHeadingStyles, baseTypeStyles } from '@components/mdx/typography'; |
|||
import { useRouter } from 'next/router'; |
|||
import { HEADER_HEIGHT } from '@components/header'; |
|||
import { CheckCircleIcon } from '@components/icons/check-circle'; |
|||
import { AlertTriangleIcon } from '@components/icons/alert-triangle'; |
|||
import { AlertCircleIcon } from '@components/icons/alert-circle'; |
|||
import { InfoCircleIcon } from '@components/icons/info-circle'; |
|||
|
|||
const preProps = { |
|||
display: 'inline-block', |
|||
border: border(), |
|||
borderRadius: '4px', |
|||
padding: '2px 6px', |
|||
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.04)', |
|||
bg: color('bg'), |
|||
}; |
|||
export const InlineCode: React.FC<BoxProps> = ({ children, ...rest }) => ( |
|||
<Text |
|||
as="code" |
|||
css={css({ |
|||
// @ts-ignore
|
|||
fontSize: '14px', |
|||
// @ts-ignore
|
|||
lineHeight: '20px', |
|||
...preProps, |
|||
...rest, |
|||
})} |
|||
> |
|||
{children} |
|||
</Text> |
|||
); |
|||
|
|||
export const Pre = (props: any) => <Text as="pre" {...props} />; |
|||
|
|||
export const SmartLink = ({ href, ...rest }: { href: string }) => { |
|||
const isExternal = href.includes('http') || href.includes('mailto'); |
|||
const link = <Link href={href} {...rest} />; |
|||
|
|||
return isExternal ? ( |
|||
link |
|||
) : ( |
|||
<NextLink href={href} passHref> |
|||
{link} |
|||
</NextLink> |
|||
); |
|||
}; |
|||
|
|||
export const Table = ({ children, ...rest }: any) => ( |
|||
<Box my={space('extra-loose')} maxWidth="100%" {...rest}> |
|||
<Box borderRadius={[0, 0, '12px']} border={border()} overflow="hidden"> |
|||
<Box overflowX="auto"> |
|||
<Box color={color('text-body')} textAlign="left" width="100%" as="table" maxWidth="100%"> |
|||
{children} |
|||
</Box> |
|||
</Box> |
|||
</Box> |
|||
</Box> |
|||
); |
|||
|
|||
export const THead = (props: any) => { |
|||
return ( |
|||
<Box |
|||
as="th" |
|||
color="var(--colors-text-caption)" |
|||
borderRight={border()} |
|||
bg={color('bg-alt')} |
|||
fontSize="12px" |
|||
px={space('base-tight')} |
|||
pt={space('tight')} |
|||
pb={space('extra-tight')} |
|||
{...props} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export const TData = (props: any) => ( |
|||
<Box |
|||
as="td" |
|||
fontSize="14px" |
|||
p={space('tight')} |
|||
px={space('base-tight')} |
|||
pt={space('base-tight')} |
|||
borderRight={border()} |
|||
borderTop={border()} |
|||
color={color('text-body')} |
|||
whiteSpace="normal" |
|||
{...props} |
|||
/> |
|||
); |
|||
|
|||
export const Link = forwardRef( |
|||
(props: { href: string; target?: string; rel?: string } & BoxProps, ref: Ref<HTMLDivElement>) => ( |
|||
<Box |
|||
as="a" |
|||
ref={ref} |
|||
color="var(--colors-accent)" |
|||
cursor="pointer" |
|||
textDecoration="underline" |
|||
_hover={{ textDecoration: 'none' }} |
|||
_focus={{ boxShadow: 'outline' }} |
|||
{...props} |
|||
/> |
|||
) |
|||
); |
|||
|
|||
export const TextItem = (props: any) => ( |
|||
<Text |
|||
mb="1em" |
|||
mt="2em" |
|||
css={{ |
|||
'&[id]': { |
|||
pointerEvents: 'none', |
|||
}, |
|||
'&[id]:before': { |
|||
display: 'block', |
|||
height: ' 6rem', |
|||
marginTop: '-6rem', |
|||
visibility: 'hidden', |
|||
content: `""`, |
|||
}, |
|||
'&[id]:hover a': { opacity: 1 }, |
|||
}} |
|||
{...props} |
|||
> |
|||
<Box |
|||
// @ts-ignore
|
|||
pointerEvents="auto" |
|||
> |
|||
{props.children} |
|||
{props.id && ( |
|||
<Box |
|||
aria-label="anchor" |
|||
as="a" |
|||
color="teal.500" |
|||
fontWeight="normal" |
|||
_focus={{ opacity: 1, boxShadow: 'outline' }} |
|||
opacity={0} |
|||
ml="0.375rem" |
|||
// @ts-ignore
|
|||
href={`#${props.id}`} |
|||
> |
|||
# |
|||
</Box> |
|||
)} |
|||
</Box> |
|||
</Text> |
|||
); |
|||
|
|||
const LinkButton = React.memo(({ link, onClick, ...rest }: BoxProps & { link: string }) => { |
|||
const url = |
|||
typeof document !== 'undefined' && document.location.origin + document.location.pathname + link; |
|||
|
|||
const { onCopy } = useClipboard(url); |
|||
const label = 'Copy url'; |
|||
return ( |
|||
<Box |
|||
as="span" |
|||
display={['none', 'none', 'block', 'block']} |
|||
onClick={e => { |
|||
onClick && onClick(e); |
|||
onCopy?.(); |
|||
}} |
|||
{...rest} |
|||
> |
|||
<Tooltip label={label} aria-label={label}> |
|||
<Link |
|||
opacity={0.5} |
|||
_hover={{ |
|||
opacity: 1, |
|||
}} |
|||
color={color('text-title')} |
|||
as="a" |
|||
href={link} |
|||
display="block" |
|||
ml={space('tight')} |
|||
> |
|||
<LinkIcon size="1rem" /> |
|||
</Link> |
|||
</Tooltip> |
|||
</Box> |
|||
); |
|||
}); |
|||
|
|||
// this is to adjust the offset of where the page scrolls to when an anchor is present
|
|||
const AnchorOffset = ({ id }: BoxProps) => |
|||
id ? ( |
|||
<Box |
|||
as="span" |
|||
display="block" |
|||
position="absolute" |
|||
style={{ userSelect: 'none', pointerEvents: 'none' }} |
|||
top={`-${HEADER_HEIGHT + 42}px`} |
|||
id={id} |
|||
/> |
|||
) : null; |
|||
|
|||
const Hashtag = () => ( |
|||
<Box position="absolute" as="span" left="10px" color={color('text-caption')}> |
|||
<HashtagIcon size="1rem" /> |
|||
</Box> |
|||
); |
|||
|
|||
export const Heading = ({ as, children, id, ...rest }: FlexProps) => { |
|||
const { isActive, doChangeActiveSlug } = useActiveHeading(id); |
|||
const [isHovered, bind] = useHover(); |
|||
const router = useRouter(); |
|||
|
|||
const link = `#${id}`; |
|||
|
|||
const handleLinkClick = () => { |
|||
void router.push(router.pathname, router.pathname + link, { shallow: true }); |
|||
doChangeActiveSlug(id); |
|||
}; |
|||
const styles = getHeadingStyles(as as any); |
|||
|
|||
return ( |
|||
<Title |
|||
as={as} |
|||
{...bind} |
|||
css={css({ |
|||
...baseTypeStyles, |
|||
...styles, |
|||
color: isActive ? color('accent') : (color('text-title') as any), |
|||
// @ts-ignore
|
|||
alignItems: 'center', |
|||
// @ts-ignore
|
|||
position: 'relative', |
|||
// @ts-ignore
|
|||
display: 'flex', |
|||
// @ts-ignore
|
|||
justifyContent: 'flex-start', |
|||
...rest, |
|||
})} |
|||
> |
|||
<Box as="span" display="inline-block"> |
|||
{children} |
|||
</Box> |
|||
<AnchorOffset id={id} /> |
|||
{id && isActive && <Hashtag />} |
|||
{id && <LinkButton opacity={isHovered ? 1 : 0} onClick={handleLinkClick} link={link} />} |
|||
</Title> |
|||
); |
|||
}; |
|||
|
|||
const BaseHeading: React.FC<BoxProps> = React.memo(props => ( |
|||
<Heading width="100%" mt={space('base-loose')} {...props} /> |
|||
)); |
|||
|
|||
export const H1: React.FC<BoxProps> = props => <BaseHeading as="h1" {...props} />; |
|||
export const H2: React.FC<BoxProps> = props => <BaseHeading as="h2" {...props} />; |
|||
export const H3: React.FC<BoxProps> = props => <BaseHeading as="h3" {...props} />; |
|||
export const H4: React.FC<BoxProps> = props => <BaseHeading as="h4" {...props} />; |
|||
export const H5: React.FC<BoxProps> = props => <BaseHeading as="h5" {...props} />; |
|||
export const H6: React.FC<BoxProps> = props => <BaseHeading as="h6" {...props} />; |
|||
|
|||
export const Br: React.FC<BoxProps> = props => <Box height="24px" {...props} />; |
|||
export const Hr: React.FC<BoxProps> = props => ( |
|||
<Box |
|||
as="hr" |
|||
borderTopWidth="1px" |
|||
borderColor={color('border')} |
|||
my={space('extra-loose')} |
|||
mx={space('extra-loose')} |
|||
{...props} |
|||
/> |
|||
); |
|||
|
|||
export const P: React.FC<BoxProps> = props => <Text as="p" {...props} />; |
|||
export const Ol: React.FC<BoxProps> = props => ( |
|||
<Box pl={space('base')} mt={space('base')} mb={space('base-tight')} as="ol" {...props} /> |
|||
); |
|||
export const Ul: React.FC<BoxProps> = props => ( |
|||
<Box pl={space('base-loose')} mt={space('base')} mb={space('base-tight')} as="ul" {...props} /> |
|||
); |
|||
export const Li: React.FC<BoxProps> = props => ( |
|||
<Box as="li" color={color('text-body')} pb={space('tight')} {...props} /> |
|||
); |
|||
|
|||
const getAlertStyles = (className: string) => { |
|||
if (className?.includes('alert-success')) { |
|||
return { |
|||
borderTopColor: themeColor('green'), |
|||
borderTopWidth: '2px', |
|||
borderTopRightRadius: '0px', |
|||
borderTopLeftRadius: '0px', |
|||
accent: themeColor('green'), |
|||
icon: CheckCircleIcon, |
|||
}; |
|||
} |
|||
if (className?.includes('alert-info')) { |
|||
return { |
|||
border: border(), |
|||
borderRadius: 'md', |
|||
boxShadow: 'mid', |
|||
accent: color('accent'), |
|||
icon: InfoCircleIcon, |
|||
}; |
|||
} |
|||
if (className?.includes('alert-warning')) { |
|||
return { |
|||
borderTopColor: '#F7AA00', |
|||
borderTopWidth: '2px', |
|||
borderTopRightRadius: '0px', |
|||
borderTopLeftRadius: '0px', |
|||
accent: '#F7AA00', |
|||
icon: AlertTriangleIcon, |
|||
}; |
|||
} |
|||
if (className?.includes('alert-danger')) { |
|||
return { |
|||
borderTopColor: themeColor('red'), |
|||
borderTopWidth: '2px', |
|||
borderTopRightRadius: '0px', |
|||
borderTopLeftRadius: '0px', |
|||
accent: themeColor('red'), |
|||
icon: AlertCircleIcon, |
|||
}; |
|||
} |
|||
return {}; |
|||
}; |
|||
|
|||
export const BlockQuote: React.FC<BoxProps> = ({ children, className, ...rest }) => { |
|||
const isAlert = className?.includes('alert'); |
|||
const { accent, icon: Icon, ...styles } = getAlertStyles(className); |
|||
return ( |
|||
<Box as="blockquote" display="block" my={space('extra-loose')} className={className} {...rest}> |
|||
<Box |
|||
border="1px solid" |
|||
css={css({ |
|||
position: 'relative', |
|||
display: 'grid', |
|||
placeItems: 'center', |
|||
gridTemplateColumns: Icon ? '22px 1fr' : '1fr', |
|||
alignItems: 'flex-start', |
|||
border: isAlert ? border() : border(), |
|||
bg: isAlert ? color('bg') : color('bg-alt'), |
|||
borderRadius: 'md', |
|||
boxShadow: isAlert ? 'mid' : 'unset', |
|||
py: space('base'), |
|||
px: space('base'), |
|||
'& p': { |
|||
flexGrow: 1, |
|||
pt: '4px', |
|||
}, |
|||
...styles, |
|||
})} |
|||
> |
|||
{Icon && ( |
|||
<Flex align="center" height="28x" flexShrink={0} color={accent} width="22px"> |
|||
<Box position="absolute" top="16px" size="22px"> |
|||
<Icon /> |
|||
</Box> |
|||
</Flex> |
|||
)} |
|||
<Box width="100%" pl={Icon && space('tight')} flexGrow={1}> |
|||
{children} |
|||
</Box> |
|||
</Box> |
|||
</Box> |
|||
); |
|||
}; |
|||
|
|||
export const Sup: React.FC<any> = props => <Text as="sup" mr={space('extra-tight')} {...props} />; |
@ -0,0 +1,102 @@ |
|||
import { Box, Flex, BoxProps, color, themeColor, space } from '@blockstack/ui'; |
|||
import React from 'react'; |
|||
|
|||
import { border } from '@common/utils'; |
|||
import { css } from '@styled-system/css'; |
|||
import { CheckCircleIcon } from '@components/icons/check-circle'; |
|||
import { AlertTriangleIcon } from '@components/icons/alert-triangle'; |
|||
import { AlertCircleIcon } from '@components/icons/alert-circle'; |
|||
import { InfoCircleIcon } from '@components/icons/info-circle'; |
|||
|
|||
const getAlertStyles = (className: string) => { |
|||
if (className?.includes('alert-success')) { |
|||
return { |
|||
borderTopColor: themeColor('green'), |
|||
borderTopWidth: '2px', |
|||
borderTopRightRadius: '0px', |
|||
borderTopLeftRadius: '0px', |
|||
accent: themeColor('green'), |
|||
icon: CheckCircleIcon, |
|||
}; |
|||
} |
|||
if (className?.includes('alert-info')) { |
|||
return { |
|||
border: border(), |
|||
borderRadius: 'md', |
|||
boxShadow: 'mid', |
|||
accent: color('accent'), |
|||
icon: InfoCircleIcon, |
|||
}; |
|||
} |
|||
if (className?.includes('alert-warning')) { |
|||
return { |
|||
borderTopColor: '#F7AA00', |
|||
borderTopWidth: '2px', |
|||
borderTopRightRadius: '0px', |
|||
borderTopLeftRadius: '0px', |
|||
accent: '#F7AA00', |
|||
icon: AlertTriangleIcon, |
|||
}; |
|||
} |
|||
if (className?.includes('alert-danger')) { |
|||
return { |
|||
borderTopColor: themeColor('red'), |
|||
borderTopWidth: '2px', |
|||
borderTopRightRadius: '0px', |
|||
borderTopLeftRadius: '0px', |
|||
accent: themeColor('red'), |
|||
icon: AlertCircleIcon, |
|||
}; |
|||
} |
|||
return {}; |
|||
}; |
|||
|
|||
export const Blockquote: React.FC<BoxProps> = React.memo( |
|||
React.forwardRef(({ children, className, ...rest }, ref) => { |
|||
const isAlert = className?.includes('alert'); |
|||
const { accent, icon: Icon, ...styles } = getAlertStyles(className); |
|||
return ( |
|||
<Box |
|||
as="blockquote" |
|||
display="block" |
|||
my={space('extra-loose')} |
|||
className={className} |
|||
ref={ref} |
|||
{...rest} |
|||
> |
|||
<Box |
|||
border="1px solid" |
|||
css={css({ |
|||
position: 'relative', |
|||
display: 'grid', |
|||
placeItems: 'center', |
|||
gridTemplateColumns: Icon ? '22px 1fr' : '1fr', |
|||
alignItems: 'flex-start', |
|||
border: isAlert ? border() : border(), |
|||
bg: isAlert ? color('bg') : color('bg-alt'), |
|||
borderRadius: 'md', |
|||
boxShadow: isAlert ? 'mid' : 'unset', |
|||
py: space('base'), |
|||
px: space('base'), |
|||
'& p': { |
|||
flexGrow: 1, |
|||
pt: '4px', |
|||
}, |
|||
...styles, |
|||
})} |
|||
> |
|||
{Icon && ( |
|||
<Flex align="center" height="28x" flexShrink={0} color={accent} width="22px"> |
|||
<Box position="absolute" top="16px" size="22px"> |
|||
<Icon /> |
|||
</Box> |
|||
</Flex> |
|||
)} |
|||
<Box width="100%" pl={Icon && space('tight')} flexGrow={1}> |
|||
{children} |
|||
</Box> |
|||
</Box> |
|||
</Box> |
|||
); |
|||
}) |
|||
); |
@ -0,0 +1,63 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Box, BoxProps, color, themeColor } from '@blockstack/ui'; |
|||
import { border } from '@common/utils'; |
|||
import { css } from '@styled-system/css'; |
|||
import { Text } from '@components/typography'; |
|||
|
|||
export const Code: React.FC<any> = React.memo( |
|||
React.forwardRef(({ children, ...rest }, ref) => { |
|||
return ( |
|||
<Box ref={ref as any} overflowX="auto"> |
|||
<Box |
|||
as="code" |
|||
css={css({ |
|||
width: '100%', |
|||
display: 'flex', |
|||
flexDirection: 'column', |
|||
minWidth: 'fit-content', |
|||
'.token-line': { |
|||
display: 'inline-block', |
|||
'&.token-line--highlighted': { |
|||
bg: 'rgba(255,255,255,0.05)', |
|||
'&::before': { |
|||
borderRightColor: themeColor('ink.600'), |
|||
}, |
|||
}, |
|||
}, |
|||
})} |
|||
{...rest} |
|||
> |
|||
<Box height="16px" width="100%" /> |
|||
{children} |
|||
<Box height="16px" width="100%" /> |
|||
</Box> |
|||
</Box> |
|||
); |
|||
}) |
|||
); |
|||
|
|||
const preProps = { |
|||
display: 'inline-block', |
|||
border: border(), |
|||
borderRadius: '4px', |
|||
padding: '2px 6px', |
|||
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.04)', |
|||
bg: color('bg'), |
|||
}; |
|||
|
|||
export const InlineCode: React.FC<BoxProps> = ({ children, ...rest }) => ( |
|||
<Text |
|||
as="code" |
|||
css={css({ |
|||
// @ts-ignore
|
|||
fontSize: '14px', |
|||
// @ts-ignore
|
|||
lineHeight: '20px', |
|||
...preProps, |
|||
...rest, |
|||
})} |
|||
> |
|||
{children} |
|||
</Text> |
|||
); |
@ -0,0 +1,117 @@ |
|||
import { Box, FlexProps, BoxProps, color, useClipboard, space } from '@blockstack/ui'; |
|||
|
|||
import React from 'react'; |
|||
import LinkIcon from 'mdi-react/LinkVariantIcon'; |
|||
import HashtagIcon from 'mdi-react/HashtagIcon'; |
|||
import { useTouchable } from '@common/hooks/use-touchable'; |
|||
import { Tooltip } from '@components/tooltip'; |
|||
import { useActiveHeading } from '@common/hooks/use-active-heading'; |
|||
import { Title } from '@components/typography'; |
|||
import { css } from '@styled-system/css'; |
|||
import { getHeadingStyles, baseTypeStyles } from '@components/mdx/typography'; |
|||
import { useRouter } from 'next/router'; |
|||
import { HEADER_HEIGHT } from '@components/header'; |
|||
import { Link } from '@components/mdx/components/link'; |
|||
|
|||
const LinkButton = React.memo(({ link, onClick, ...rest }: BoxProps & { link: string }) => { |
|||
const url = |
|||
typeof document !== 'undefined' && document.location.origin + document.location.pathname + link; |
|||
|
|||
const { onCopy } = useClipboard(url); |
|||
const label = 'Copy url'; |
|||
return ( |
|||
<Box |
|||
as="span" |
|||
display={['none', 'none', 'block', 'block']} |
|||
onClick={e => { |
|||
onClick && onClick(e); |
|||
onCopy?.(); |
|||
}} |
|||
{...rest} |
|||
> |
|||
<Tooltip label={label} aria-label={label}> |
|||
<Link |
|||
opacity={0.5} |
|||
_hover={{ |
|||
opacity: 1, |
|||
}} |
|||
color={color('text-title')} |
|||
as="a" |
|||
href={link} |
|||
display="block" |
|||
ml={space('tight')} |
|||
> |
|||
<LinkIcon size="1rem" /> |
|||
</Link> |
|||
</Tooltip> |
|||
</Box> |
|||
); |
|||
}); |
|||
|
|||
// this is to adjust the offset of where the page scrolls to when an anchor is present
|
|||
const AnchorOffset = ({ id }: BoxProps) => |
|||
id ? ( |
|||
<Box |
|||
as="span" |
|||
display="block" |
|||
position="absolute" |
|||
style={{ userSelect: 'none', pointerEvents: 'none' }} |
|||
top={`-${HEADER_HEIGHT + 42}px`} |
|||
id={id} |
|||
/> |
|||
) : null; |
|||
|
|||
const Hashtag = () => ( |
|||
<Box position="absolute" as="span" left="10px" color={color('text-caption')}> |
|||
<HashtagIcon size="1rem" /> |
|||
</Box> |
|||
); |
|||
|
|||
export const Heading = ({ as, children, id, ...rest }: FlexProps) => { |
|||
const { isActive, doChangeActiveSlug } = useActiveHeading(id); |
|||
|
|||
const { bind, hover, active } = useTouchable({ |
|||
behavior: 'link', |
|||
}); |
|||
const router = useRouter(); |
|||
|
|||
const link = `#${id}`; |
|||
|
|||
const handleLinkClick = () => { |
|||
void router.push(router.pathname, router.pathname + link, { shallow: true }); |
|||
doChangeActiveSlug(id); |
|||
}; |
|||
const styles = getHeadingStyles(as as any); |
|||
|
|||
return ( |
|||
<Title |
|||
as={as} |
|||
{...bind} |
|||
css={css({ |
|||
...baseTypeStyles, |
|||
...styles, |
|||
color: isActive ? color('accent') : (color('text-title') as any), |
|||
// @ts-ignore
|
|||
alignItems: 'center', |
|||
// @ts-ignore
|
|||
position: 'relative', |
|||
// @ts-ignore
|
|||
display: 'flex', |
|||
// @ts-ignore
|
|||
justifyContent: 'flex-start', |
|||
...rest, |
|||
})} |
|||
> |
|||
<Box as="span" display="inline-block"> |
|||
{children} |
|||
</Box> |
|||
<AnchorOffset id={id} /> |
|||
{id && isActive && <Hashtag />} |
|||
{id && <LinkButton opacity={hover || active ? 1 : 0} onClick={handleLinkClick} link={link} />} |
|||
</Title> |
|||
); |
|||
}; |
|||
|
|||
export const BaseHeading: React.FC<BoxProps> = React.memo(props => ( |
|||
<Heading width="100%" mt={space('base-loose')} {...props} /> |
|||
)); |
@ -0,0 +1,7 @@ |
|||
export * from './blockquote'; |
|||
export * from './code'; |
|||
export * from './heading'; |
|||
export * from './link'; |
|||
export * from './list'; |
|||
export * from './table'; |
|||
export * from './typography'; |
@ -0,0 +1,31 @@ |
|||
import { Box, BoxProps } from '@blockstack/ui'; |
|||
import NextLink from 'next/link'; |
|||
import React, { forwardRef, Ref } from 'react'; |
|||
|
|||
export const SmartLink = ({ href, ...rest }: { href: string }) => { |
|||
const isExternal = href.includes('http') || href.includes('mailto'); |
|||
const link = <Link href={href} {...rest} />; |
|||
|
|||
return isExternal ? ( |
|||
link |
|||
) : ( |
|||
<NextLink href={href} passHref> |
|||
{link} |
|||
</NextLink> |
|||
); |
|||
}; |
|||
|
|||
export const Link = forwardRef( |
|||
(props: { href: string; target?: string; rel?: string } & BoxProps, ref: Ref<HTMLDivElement>) => ( |
|||
<Box |
|||
as="a" |
|||
ref={ref} |
|||
color="var(--colors-accent)" |
|||
cursor="pointer" |
|||
textDecoration="underline" |
|||
_hover={{ textDecoration: 'none' }} |
|||
_focus={{ boxShadow: 'outline' }} |
|||
{...props} |
|||
/> |
|||
) |
|||
); |
@ -0,0 +1,14 @@ |
|||
import { Box, BoxProps, color, space } from '@blockstack/ui'; |
|||
import React, { forwardRef, Ref } from 'react'; |
|||
|
|||
export const Ol: React.FC<BoxProps> = props => ( |
|||
<Box pl={space('base')} mt={space('base')} mb={space('base-tight')} as="ol" {...props} /> |
|||
); |
|||
|
|||
export const Ul: React.FC<BoxProps> = props => ( |
|||
<Box pl={space('base-loose')} mt={space('base')} mb={space('base-tight')} as="ul" {...props} /> |
|||
); |
|||
|
|||
export const Li: React.FC<BoxProps> = props => ( |
|||
<Box as="li" color={color('text-body')} pb={space('tight')} {...props} /> |
|||
); |
@ -0,0 +1,48 @@ |
|||
import { Box, color, space } from '@blockstack/ui'; |
|||
import React from 'react'; |
|||
import { P } from '@components/mdx/components'; |
|||
import { border } from '@common/utils'; |
|||
|
|||
export const Table = ({ children, ...rest }: any) => ( |
|||
<Box my={space('extra-loose')} maxWidth="100%" {...rest}> |
|||
<Box borderRadius={[0, 0, '12px']} border={border()} overflow="hidden"> |
|||
<Box overflowX="auto"> |
|||
<Box color={color('text-body')} textAlign="left" width="100%" as="table" maxWidth="100%"> |
|||
{children} |
|||
</Box> |
|||
</Box> |
|||
</Box> |
|||
</Box> |
|||
); |
|||
|
|||
export const THead = (props: any) => { |
|||
return ( |
|||
<Box |
|||
as="th" |
|||
color="var(--colors-text-caption)" |
|||
borderRight={border()} |
|||
bg={color('bg-alt')} |
|||
fontSize="12px" |
|||
px={space('base-tight')} |
|||
pt={space('tight')} |
|||
pb={space('extra-tight')} |
|||
{...props} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export const TData = (props: any) => ( |
|||
<Box |
|||
as="td" |
|||
fontSize="14px" |
|||
p={space('tight')} |
|||
px={space('base-tight')} |
|||
pt={space('base-tight')} |
|||
borderRight={border()} |
|||
borderTop={border()} |
|||
color={color('text-body')} |
|||
whiteSpace="normal" |
|||
> |
|||
<P {...props} /> |
|||
</Box> |
|||
); |
@ -0,0 +1,28 @@ |
|||
import { Box, BoxProps, color, space } from '@blockstack/ui'; |
|||
import React from 'react'; |
|||
import { Text } from '@components/typography'; |
|||
import { BaseHeading } from '@components/mdx/components/heading'; |
|||
|
|||
export const H1: React.FC<BoxProps> = props => <BaseHeading as="h1" {...props} />; |
|||
export const H2: React.FC<BoxProps> = props => <BaseHeading as="h2" {...props} />; |
|||
export const H3: React.FC<BoxProps> = props => <BaseHeading as="h3" {...props} />; |
|||
export const H4: React.FC<BoxProps> = props => <BaseHeading as="h4" {...props} />; |
|||
export const H5: React.FC<BoxProps> = props => <BaseHeading as="h5" {...props} />; |
|||
export const H6: React.FC<BoxProps> = props => <BaseHeading as="h6" {...props} />; |
|||
|
|||
export const Br: React.FC<BoxProps> = props => <Box height="24px" {...props} />; |
|||
export const Hr: React.FC<BoxProps> = props => ( |
|||
<Box |
|||
as="hr" |
|||
borderTopWidth="1px" |
|||
borderColor={color('border')} |
|||
my={space('extra-loose')} |
|||
mx={space('extra-loose')} |
|||
{...props} |
|||
/> |
|||
); |
|||
|
|||
export const P: React.FC<BoxProps> = props => <Text as="p" {...props} />; |
|||
|
|||
export const Pre = (props: any) => <Text as="pre" {...props} />; |
|||
export const Sup: React.FC<any> = props => <Text as="sup" mr={space('extra-tight')} {...props} />; |
Loading…
Reference in new issue