fix(ui-docs): improve typography, styles, write color-modes.mdx

This commit is contained in:
Thomas Osmonson
2020-06-24 14:24:36 -05:00
committed by Thomas Osmonson
parent 67c7f31357
commit 7870a0f45e
57 changed files with 1656 additions and 2105 deletions

View File

@@ -6,32 +6,13 @@ jobs:
docs-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set Node Version
uses: actions/setup-node@v1
env:
RUNNER_TEMP: /tmp
with:
node-version: 12.16.1
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Monorepo Dependencies
run: yarn
- name: Lerna Bootstrap
run: yarn lerna bootstrap
- uses: amondnet/vercel-action@v19
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
github-token: ${{ secrets.GITHUB_TOKEN }} #Optional
vercel-args: '--prod' #Optional
vercel-org-id: ${{ secrets.ORG_ID}} #Required
vercel-project-id: ${{ secrets.PROJECT_ID}} #Required
working-directory: ./packages/ui-docs
- uses: actions/checkout@v2
- uses: amondnet/vercel-action@v19.0.1+1
id: vercel-action
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
working-directory: packages/ui-docs
scope: ${{ secrets.VERCEL_SCOPE }}

View File

@@ -1,24 +1,32 @@
const withTM = require('./with-tm')(['@blockstack/ui']);
const withPlugins = require('next-compose-plugins');
const withMdxEnhanced = require('next-mdx-enhanced');
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
const remarkPlugins = [
require('remark-autolink-headings'),
require('remark-external-links'),
require('remark-emoji'),
require('remark-images'),
require('remark-slug'),
require('remark-unwrap-images'),
require('remark-slug'),
];
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
module.exports = withBundleAnalyzer(
withMdxEnhanced({
layoutPath: 'src/components/layouts',
defaultLayout: true,
fileExtensions: ['mdx'],
remarkPlugins,
},
});
module.exports = withMDX(
withTM({
extendFrontMatter: {
process: mdxContent => {
const regex = /\n(#+)(.*)/gm;
const found = mdxContent.match(regex);
const headings = found && found.length ? found.map(f => f.split('# ')[1]) : [];
return {
headings,
};
},
},
})({
experimental: {
modern: true,
polyfillsOptimization: true,
@@ -26,11 +34,36 @@ module.exports = withMDX(
},
pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
webpack: (config, options) => {
const aliases = config.resolve.alias || (config.resolve.alias = {});
aliases['@blockstack/ui'] = require.resolve('../ui');
if (!options.isServer) {
config.node['fs'] = 'empty';
}
if (!options.dev) {
const splitChunks = config.optimization && config.optimization.splitChunks;
if (splitChunks) {
const cacheGroups = splitChunks.cacheGroups;
const preactModules = /[\\/]node_modules[\\/](preact|preact-render-to-string|preact-context-provider)[\\/]/;
if (cacheGroups.framework) {
cacheGroups.preact = Object.assign({}, cacheGroups.framework, {
test: preactModules,
});
cacheGroups.commons.name = 'framework';
} else {
cacheGroups.preact = {
name: 'commons',
chunks: 'all',
test: preactModules,
};
}
}
// Install webpack aliases:
const aliases = config.resolve.alias || (config.resolve.alias = {});
aliases.react = aliases['react-dom'] = 'preact/compat';
// https://github.com/FormidableLabs/react-live#what-bundle-size-can-i-expect
aliases['buble'] = '@philpl/buble';
}
return config;
},
})

View File

@@ -2,45 +2,47 @@
"name": "@blockstack/ui-docs",
"version": "0.1.9",
"dependencies": {
"@blockstack/ui": "2.7.0",
"@blockstack/ui": "^2.7.2-beta.2",
"@mdx-js/loader": "1.6.6",
"@mdx-js/mdx": "^1.6.6",
"@mdx-js/react": "^1.6.6",
"@next/mdx": "^9.4.4",
"@octokit/rest": "^17.9.2",
"@philpl/buble": "^0.19.7",
"@reach/tooltip": "^0.10.5",
"@styled-system/theme-get": "^5.1.2",
"@types/mdx-js__react": "^1.5.2",
"@types/npm-registry-fetch": "^8.0.0",
"anser": "^1.4.9",
"formik": "^2.1.4",
"js-levenshtein": "^1.1.6",
"@types/node": "^14.0.14",
"@types/nprogress": "^0.2.0",
"@types/reach__tooltip": "^0.2.0",
"mdi-react": "^7.3.0",
"next": "^9.4.5-canary.3",
"next-compose-plugins": "^2.2.0",
"next-seo": "4.5.0",
"next-transpile-modules": "^3.3.0",
"npm-registry-fetch": "^8.1.0",
"next-mdx-enhanced": "^3.0.0",
"nookies": "^2.3.2",
"nprogress": "^0.2.0",
"preact": "^10.4.4",
"preact-render-to-string": "^5.1.4",
"prettier": "^2.0.5",
"prism-react-renderer": "^1.0.2",
"react-children-utilities": "^2.1.2",
"react-icons": "^3.9.0",
"react-live": "^2.2.2",
"react-simple-code-editor": "^0.11.0",
"react-view": "^2.3.2",
"remark-autolink-headings": "6.0.1",
"react-ssr-prepass": "npm:preact-ssr-prepass@^1.0.1",
"remark-emoji": "2.1.0",
"remark-external-links": "^6.1.0",
"remark-images": "2.0.0",
"remark-slug": "6.0.0",
"remark-unwrap-images": "2.0.0",
"socks-proxy-agent": "^5.0.0",
"stacktrace-parser": "^0.1.10",
"strip-ansi": "^6.0.0",
"store": "^2.0.12",
"typeface-fira-code": "^1.1.4",
"typeface-inter": "^3.12.0",
"typescript": "^3.9.5",
"use-events": "^1.4.1"
},
"devDependencies": {
"@next/bundle-analyzer": "^9.4.4",
"babel-plugin-styled-components": "^1.10.7",
"next-transpile-modules": "^3.3.0",
"now": "^19.0.1",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"styled-components": "^5.0.1",
@@ -48,10 +50,11 @@
},
"private": true,
"scripts": {
"build": "next build",
"build": "next build && next export -o build",
"build:analyze": "next telemetry disable && ANALYZE=true next build",
"start": "NODE_ENV=production next start",
"dev": "next dev",
"export": "next export",
"start": "next start",
"lint": "yarn run lint:eslint && yarn run lint:prettier",
"lint:eslint": "eslint --ext .ts,.tsx ./src",
"lint:fix": "eslint --ext .ts,.tsx ./src/ -f unix --fix && prettier --write src/**/*.{ts,tsx} *.js",

View File

@@ -1,14 +0,0 @@
const SEO = {
title: 'Waffle Design System',
description:
'Simple, Modular and Accessible UI Components for your React Applications. Built with Styled System',
openGraph: {
url: 'https://waffle.blockstack.sh',
title: 'Blockstack UI',
description:
'Simple, Modular and Accessible UI Components for your React Applications. Built with Styled System',
site_name: 'Blockstack UI',
},
};
export default SEO;

View File

@@ -1,41 +1,23 @@
import React, { useCallback, useEffect } from 'react';
import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAppState } from '@common/hooks/use-app-state';
import { State } from '@components/app-state/types';
interface ActiveHeadingProps {
slug: string;
}
type ActiveHeadingReturn = [boolean, (value: any) => void];
type ActiveHeadingReturn = [boolean, (value: string) => void, string];
export const useActiveHeading = ({ slug }: ActiveHeadingProps): ActiveHeadingReturn => {
export const useActiveHeading = (_slug: string): ActiveHeadingReturn => {
const router = useRouter();
const state = useAppState();
const { setState } = state;
const { asPath } = router;
const { activeSlug, doChangeActiveSlug } = useAppState();
const urlHash = asPath?.includes('#') && asPath.split('#')[1];
const location = typeof window !== 'undefined' && window.location.href;
useEffect(() => {
if (
asPath &&
asPath.includes('#') &&
asPath.split('#')[1] === slug &&
state.activeSlug !== slug
) {
setState((s: State) => ({ ...s, activeSlug: slug }));
if (urlHash && !activeSlug) {
doChangeActiveSlug(urlHash);
}
if (asPath && !asPath.includes('#')) {
setState((s: State) => ({ ...s, activeSlug: '' }));
}
}, [asPath]);
}, [asPath, urlHash, location]);
const handleStateUpdate = useCallback(
newSlug => setState((s: State) => ({ ...s, activeSlug: newSlug })),
[state.activeSlug]
);
const isActive = _slug === activeSlug;
const selectActiveSlug = useCallback((state: State) => state && state.activeSlug, []);
const isActive = selectActiveSlug(state) === slug;
return [isActive, handleStateUpdate];
return [isActive, doChangeActiveSlug, location];
};

View File

@@ -2,4 +2,25 @@ import React from 'react';
import { AppStateContext } from '@components/app-state/context';
import { State } from '@components/app-state/types';
export const useAppState = (): State => React.useContext(AppStateContext);
interface UseAppStateReturn extends State {
doChangeActiveSlug: (activeSlug: string) => void;
}
export const useAppState = (): UseAppStateReturn => {
const { setState, ...rest } = React.useContext(AppStateContext);
const doChangeActiveSlug = React.useCallback(
(activeSlug: string) =>
setState((state: State) => ({
...state,
activeSlug,
})),
[]
);
return {
...rest,
doChangeActiveSlug,
setState,
};
};

View File

@@ -1,7 +0,0 @@
import { useContext } from 'react';
import { ColorModeContext } from '@components/color-modes';
export const useColorMode = () => {
const { colorMode, toggleColorMode } = useContext(ColorModeContext);
return { colorMode, toggleColorMode };
};

View File

@@ -10,6 +10,7 @@ export const components = [
'Box',
'Button',
'CodeBlock',
'Color modes',
'CSS Reset',
'Flex',
'Grid',
@@ -35,11 +36,19 @@ export const hooks = [
'useId',
'useLatestRef',
'useMergeRefs',
'useTheme',
];
export const bottomNavLinks = ['Contributing', 'Further reading', 'GitHub'];
export const bottomNavLinks = ['Contributing', 'Further reading'];
export const routes = [...topNavLinks, ...components, ...bottomNavLinks];
export const routes = [...topNavLinks, ...components, ...hooks, ...bottomNavLinks];
export const paginationRoutes = {
top: topNavLinks,
components,
hooks,
bottom: bottomNavLinks,
};
export const links = {
github: 'https://github.com/blockstack/ux',

View File

@@ -0,0 +1,61 @@
import { Children, isValidElement, ReactNode, ReactElement, ReactText } from 'react';
import { BorderStyleProperty } from 'csstype';
import { ColorsStringLiteral, color } from '@blockstack/ui';
const camelToKebab = (string: string) =>
string
.toString()
.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2')
.toLowerCase();
export const slugify = (string: string) =>
string
.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
export const border = (
width = 1,
style: BorderStyleProperty = 'solid',
_color: ColorsStringLiteral = 'border'
): string => `${width}px ${style} ${color(_color)}`;
// https://github.com/fernandopasik/react-children-utilities/blob/master/src/lib/hasChildren.ts
const hasChildren = (element: ReactNode): element is ReactElement<{ children: ReactNode[] }> =>
isValidElement<{ children?: ReactNode[] }>(element) && Boolean(element.props.children);
// https://github.com/fernandopasik/react-children-utilities/blob/master/src/lib/onlyText.ts
export const childToString = (child?: ReactText | boolean | {} | null): string => {
if (typeof child === 'undefined' || child === null || typeof child === 'boolean') {
return '';
}
if (JSON.stringify(child) === '{}') {
return '';
}
return (child as string | number).toString();
};
export const onlyText = (children: ReactNode): string => {
if (!(children instanceof Array) && !isValidElement(children)) {
return childToString(children);
}
return Children.toArray(children).reduce((text: string, child: ReactNode): string => {
let newText = '';
if (isValidElement(child) && hasChildren(child)) {
newText = onlyText(child.props.children);
} else if (isValidElement(child) && !hasChildren(child)) {
newText = '';
} else {
newText = childToString(child);
}
return text.concat(newText);
}, '') as string;
};

View File

@@ -1,61 +0,0 @@
import { BorderStyleProperty } from 'csstype';
import { ColorsStringLiteral } from '@components/color-modes';
export const slugify = (string: string) =>
string
.toString()
.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
/**
* Style utils
*
* color={color('bg-alt')} // returns css var --var-colors-bg-alt
* marginTop={space('extra-loose')} // maps to size in theme
* border={border()} // default border
*/
enum NamedSpacing {
ExtraTight = 'extra-tight',
Tight = 'tight',
BaseTight = 'base-tight',
Base = 'base',
BaseLoose = 'base-loose',
Loose = 'loose',
ExtraLoose = 'extra-loose',
}
type NamedSpacingLiteral =
| 'none'
| 'extra-tight'
| 'tight'
| 'base-tight'
| 'base'
| 'base-loose'
| 'loose'
| 'extra-loose';
type Spacing = NamedSpacingLiteral;
type SpacingTypes =
| Spacing
| [Spacing]
| [Spacing, Spacing]
| [Spacing, Spacing, Spacing]
| [Spacing, Spacing, Spacing, Spacing];
export const space = (spacing: SpacingTypes): SpacingTypes => spacing;
export const color = (name: ColorsStringLiteral) => {
return `var(--colors-${name})`;
};
export const border = (
width = 1,
style: BorderStyleProperty = 'solid',
_color: ColorsStringLiteral = 'border'
): string => `${width}px ${style} ${color(_color)}`;

View File

@@ -1,9 +1,11 @@
import React, { useState, useContext } from 'react';
import { LiveProvider, withLive, LiveError, LiveContext, LivePreview } from 'react-live';
import { Box, CodeBlock as BaseCodeBlock } from '@blockstack/ui';
import React, { useContext } from 'react';
import { LiveProvider, LiveContext, LivePreview } from 'react-live';
import { Box, CodeBlock as BaseCodeBlock, space, color } from '@blockstack/ui';
import 'prismjs/components/prism-jsx';
import { CodeEditor } from '@components/code-editor';
import { Caption } from '@components/typography';
import 'prismjs/components/prism-tsx';
import { border } from '@common/utils';
const Error = (props: any) => {
const { error } = useContext(LiveContext);
@@ -32,28 +34,40 @@ export const liveErrorStyle = {
};
export const LiveCodePreview = (props: any) => (
<Box
as={LivePreview}
fontFamily="body"
borderRadius="6px"
p="base"
border="1px solid var(--colors-border)"
{...props}
/>
<Box fontFamily="body">
<Box
as={LivePreview}
boxShadow="mid"
border={border()}
borderRadius="6px"
p={space('base')}
mb={space('base')}
{...props}
/>
</Box>
);
export const JsxEditor = ({ liveProviderProps, editorCode, handleCodeChange, language }) => (
export const JsxEditor = ({
liveProviderProps,
editorCode,
handleCodeChange,
language,
...rest
}) => (
<LiveProvider {...liveProviderProps}>
<Box mt="base" pl="base">
<Caption fontFamily="body">Preview</Caption>
<Box mb={space('tight')} mt={space('base')}>
<Caption fontWeight={500} pl={space('tight')} fontFamily="body">
Preview
</Caption>
</Box>
<LiveCodePreview />
<Box mt="base" tabIndex={-1} position="relative">
<Box pl="base">
<Caption fontFamily="body">Editable example</Caption>
</Box>
<CodeEditor value={editorCode} onChange={handleCodeChange} language={language} />
<Box mb={space('tight')}>
<Caption fontWeight={500} pl={space('tight')} fontFamily="body">
Editable example
</Caption>
</Box>
<CodeEditor value={editorCode} onChange={handleCodeChange} language={language} />
<Error />
</LiveProvider>
);
@@ -68,7 +82,10 @@ export const Preview = ({ liveProviderProps }) => (
export const SimpleCodeBlock = ({ editorCode, language }) => (
<BaseCodeBlock
border="1px solid var(--colors-border)"
borderTop={border()}
borderBottom={border()}
borderLeft={['none', border(), border()]}
borderRight={['none', border(), border()]}
code={editorCode}
language={language}
my="base"

View File

@@ -70,7 +70,7 @@ export const CodeEditor = React.memo((props: CodeEditorProps) => {
<Box
className="code-editor"
bg="ink"
borderRadius="6px"
borderRadius={['unset', 'unset', '6px', '6px']}
py="base-tight"
border="1px solid var(--colors-border)"
overflowX="auto"

View File

@@ -1,8 +1,7 @@
import React, { forwardRef, Ref } from 'react';
import { LinkProps } from '@components/typography';
import { useColorMode } from '@blockstack/ui';
import { DarkModeIcon } from '@components/icons/dark-mode';
import { useColorMode } from '@common/hooks/use-color-mode';
import { LightModeIcon } from '@components/icons/light-mode';
import { IconButton } from '@components/icon-button';

View File

@@ -1,212 +0,0 @@
import React, { useCallback } from 'react';
import { createGlobalStyle } from 'styled-components';
import { themeGet } from '@styled-system/theme-get';
import { useMediaQuery } from '@common/hooks/use-media-query';
import { Theme } from '@blockstack/ui';
import { color } from '@common/utils';
export { color };
export const colorGet = (path: string, fallback?: string) => themeGet('colors.' + path, fallback);
enum Color {
Accent = 'accent',
Bg = 'bg',
BgAlt = 'bg-alt',
BgLight = 'bg-light',
Invert = 'invert',
TextHover = 'text-hover',
TextTitle = 'text-title',
TextCaption = 'text-caption',
TextBody = 'text-body',
InputPlaceholder = 'input-placeholder',
Border = 'border',
FeedbackAlert = 'feedback-alert',
FeedbackError = 'feedback-error',
FeedbackSuccess = 'feedback-success',
}
export type ColorsStringLiteral =
| 'accent'
| 'bg'
| 'bg-alt'
| 'bg-light'
| 'invert'
| 'text-hover'
| 'text-title'
| 'text-caption'
| 'text-body'
| 'input-placeholder'
| 'border'
| 'feedback-alert'
| 'feedback-error'
| 'feedback-success';
type ColorModeTypes = {
[key in ColorsStringLiteral]: string;
};
interface ColorModesInterface {
light: ColorModeTypes;
dark: ColorModeTypes;
}
const colors = (props: { theme: Theme }): ColorModesInterface => ({
light: {
[Color.Accent]: colorGet('blue')(props),
[Color.Bg]: 'white',
[Color.BgAlt]: colorGet('ink.50')(props),
[Color.BgLight]: 'white',
[Color.Invert]: colorGet('ink')(props),
[Color.TextHover]: colorGet('blue')(props),
[Color.TextTitle]: colorGet('ink')(props),
[Color.TextCaption]: colorGet('ink.600')(props),
[Color.TextBody]: colorGet('ink.900')(props),
[Color.InputPlaceholder]: colorGet('ink.400')(props),
[Color.Border]: 'rgb(229, 229, 236)',
[Color.FeedbackAlert]: colorGet('orange')(props),
[Color.FeedbackError]: colorGet('red')(props),
[Color.FeedbackSuccess]: colorGet('green')(props),
},
dark: {
[Color.Accent]: colorGet('blue.400')(props),
[Color.Bg]: colorGet('ink')(props),
[Color.BgAlt]: 'rgba(255,255,255,0.05)',
[Color.BgLight]: 'rgba(255,255,255,0.08)',
[Color.Invert]: 'white',
[Color.TextHover]: colorGet('blue.300')(props),
[Color.TextTitle]: 'white',
[Color.TextCaption]: '#a7a7ad',
[Color.TextBody]: colorGet('ink.300')(props),
[Color.InputPlaceholder]: 'rgba(255,255,255,0.3)',
[Color.Border]: 'rgb(39, 41, 46)',
[Color.FeedbackAlert]: colorGet('orange')(props),
[Color.FeedbackError]: colorGet('red')(props),
[Color.FeedbackSuccess]: colorGet('green')(props),
},
});
const colorModeStyles = (props: { theme: Theme; colorMode: 'light' | 'dark' }) =>
colors(props)[props.colorMode];
const colorMap = (props: { theme: Theme; colorMode: 'light' | 'dark' }) =>
Object.keys(colors(props)[props.colorMode]);
export const ColorModes = createGlobalStyle`
:root{
${({ colorMode = 'light', ...rest }: any) =>
colorMap({ colorMode, ...rest }).map(key => {
return `--colors-${key}: ${
//@ts-ignore
colorModeStyles({ colorMode, ...rest })[key as string]
};`;
})}
}
@media (prefers-color-scheme: dark) {
:root {
${({ colorMode = 'light', ...rest }: any) =>
colorMap({ colorMode, ...rest }).map(key => {
return `--colors-${key}: ${
//@ts-ignore
colorModeStyles({ colorMode, ...rest })[key as string]
};`;
})}
}
}
@media (prefers-color-scheme: light) {
:root {
${({ colorMode = 'light', ...rest }: any) =>
colorMap({ colorMode, ...rest }).map(key => {
return `--colors-${key}: ${
//@ts-ignore
colorModeStyles({ colorMode, ...rest })[key as string]
};`;
})}
}
}
html, body, #__next {
background: var(--colors-bg);
border-color: var(--colors-border);
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus {
-webkit-text-fill-color: var(--colors-text-body);
font-size: 16px !important;
transition: background-color 5000s ease-in-out 0s;
}
input:-ms-input-placeholder,
textarea:-ms-input-placeholder {
color: var(--colors-input-placeholder) !important;
}
input::-ms-input-placeholder,
textarea::-ms-input-placeholder {
color: var(--colors-input-placeholder) !important;
}
input::placeholder,
textarea::placeholder {
color: var(--colors-input-placeholder) !important;
}
`;
export const ColorModeContext = React.createContext<{ colorMode?: string; toggleColorMode?: any }>({
colorMode: undefined,
});
export const ColorModeProvider = ({
colorMode,
children,
}: {
colorMode?: string;
children: any;
}) => {
const [mode, setMode] = React.useState(colorMode);
const [darkmode] = useMediaQuery('(prefers-color-scheme: dark)');
const [lightmode] = useMediaQuery('(prefers-color-scheme: light)');
const setColorMode = useCallback(
(mode: 'light' | 'dark') => {
setMode(mode);
},
[mode]
);
const toggleColorMode = useCallback(() => {
if (mode === 'light') {
setColorMode('dark');
return;
}
if (mode === 'dark') {
setColorMode('light');
return;
}
if (!colorMode && darkmode) {
setColorMode('light');
return;
}
if (!mode && lightmode) {
setColorMode('dark');
return;
}
}, [mode, lightmode, darkmode]);
return (
<ColorModeContext.Provider value={{ colorMode: mode, toggleColorMode }}>
<ColorModes colorMode={mode} />
{children}
</ColorModeContext.Provider>
);
};

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { Box, Flex, useTheme } from '@blockstack/ui';
import { Box, Flex, useTheme, space, colorGet, theme } from '@blockstack/ui';
import { Text } from '@components/typography';
import { space } from '@common/utils';
import { InlineCode } from '@components/mdx';
export const ColorPalette = ({ color, isString = false, name, ...props }: any) => {
const theme = useTheme();
let colorCode = color;
const [shade, hue] = color.split('.');
@@ -18,45 +17,26 @@ export const ColorPalette = ({ color, isString = false, name, ...props }: any) =
if (!colorCode) return null;
return (
<Flex
flexWrap={['wrap', 'nowrap', 'nowrap']}
p={space('base')}
align="center"
justify="center"
flexDir="column"
bg="white"
borderRadius="12px"
mb={space('base-loose')}
boxShadow="mid"
_hover={{
boxShadow: 'high',
cusor: 'pointer',
}}
{...props}
>
<Box
boxShadow="mid"
flexShrink={0}
borderRadius="100%"
size={['32px', '32px', '64px']}
bg={color}
mb={space('tight')}
/>
const getColorCode = (col: string) => {
if (col.includes('.')) {
const key = col.split('.')[0];
const number = col.split('.')[1];
return theme.colors[key][number];
}
return theme.colors[col] && theme.colors[col].toString();
};
<Flex flexDirection="column" justify="center" align="center" textAlign="center">
return (
<Box {...props}>
<Flex align="center" justify="center" flexDirection="column" height="128px" bg={colorCode}>
<Box>
<Text color="ink" fontWeight={600} textTransform="capitalize">
{name}
</Text>
<InlineCode>{color}</InlineCode>
</Box>
<Box>
<Text textTransform="uppercase" textStyle="caption" color="#767A85">
{isString ? theme.colors[color] : colorCode}
</Text>
<InlineCode>{getColorCode(color)}</InlineCode>
</Box>
</Flex>
</Flex>
</Box>
);
};
@@ -115,12 +95,5 @@ export const Colors = ({ colors = ['blue', 'ink', 'feedback', 'darken'], ...rest
};
export const ColorWrapper = ({ isOdd, ...rest }: any) => (
<Box
flexShrink={0}
mt={space('extra-loose')}
pr={[isOdd ? space('extra-loose') : 0, space('base'), space('extra-loose')]}
width={['50%', '25%', '25%']}
minWidth="150px"
{...rest}
/>
<Box flexShrink={0} mt={space('extra-loose')} width={'50%'} {...rest} />
);

View File

@@ -1,16 +1,13 @@
import React from 'react';
import { Flex, FlexProps } from '@blockstack/ui';
import { space } from '@common/utils';
import { Flex, FlexProps, space } from '@blockstack/ui';
const ContentWrapper: React.FC<FlexProps> = props => (
<Flex
width="100%"
maxWidth="72ch"
flexShrink={1}
px={space('base')}
pt={space(['base', 'base', 'extra-loose'])}
mt={space('extra-loose')}
pb={[4, 4, 6]}
mx="auto"
flexDirection="column"
{...props}
/>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Flex } from '@blockstack/ui';
import { Flex, Box, color, space } from '@blockstack/ui';
import { SideNav } from './side-nav';
import { Header } from './header';
import { Main } from './main';
@@ -8,8 +8,78 @@ import { useRouter } from 'next/router';
import { WaffleHeader } from './waffle-header';
import { ContentWrapper } from './content-wrapper';
import NotFoundPage from '@pages/404';
import { createGlobalStyle } from 'styled-components';
import { slugify } from '@common/utils';
import { Text } from '@components/typography';
import { Link } from '@components/mdx';
import { useActiveHeading } from '@common/hooks/use-active-heading';
import { css } from '@styled-system/css';
export const MdxOverrides = createGlobalStyle`
const DocsLayout: React.FC = ({ children }) => {
html {
scroll-behavior: smooth;
}
pre{
display: inline-block;
}
p, ul, ol, table {
color: ${color('text-body')};
a > pre {
color: ${color('accent')} !important;
}
}
`;
const Item = ({ slug, label }) => {
const [isActive, setActiveSlug] = useActiveHeading(slug);
return (
<Box py={space('extra-tight')}>
<Link
href={`#${slug}`}
fontSize="14px"
color={isActive ? color('text-title') : color('text-caption')}
fontWeight={isActive ? '600' : '400'}
onClick={() => setActiveSlug(slug)}
textDecoration="none"
_hover={{
textDecoration: 'underline',
color: color('accent'),
}}
pointerEvents={isActive ? 'none' : 'unset'}
>
{label}
</Link>
</Box>
);
};
const TableOfContents = ({ headings }: { headings?: string[] }) => {
return (
<Box position="relative">
<Box
mt="50px"
flexShrink={0}
display={['none', 'none', 'block', 'block']}
minWidth={['100%', '200px', '200px']}
position="sticky"
top="118px"
>
<Box mb={space('extra-tight')}>
<Text fontWeight="bold" fontSize="14px">
On this page
</Text>
</Box>
{headings.map((heading, index) => {
return index > 0 ? <Item slug={slugify(heading)} label={heading} key={index} /> : null;
})}
</Box>
</Box>
);
};
const DocsLayout: React.FC<{ headings?: string[] }> = ({ children, headings }) => {
const router = useRouter();
let isErrorPage = false;
@@ -20,24 +90,65 @@ const DocsLayout: React.FC = ({ children }) => {
}
});
return (
<>
<Flex minHeight="100vh" flexDirection="column">
<Header />
<Flex minHeight="100vh">
<Flex width="100%" flexGrow={1}>
<SideNav display={['none', 'none', 'block']} />
<Main
maxWidth={['100%', '100%', 'calc(100% - 240px)']}
width="100%"
<Flex
flexGrow={1}
maxWidth={['100%', '100%', 'calc(100% - 200px)', 'calc(100% - 200px)']}
mt={'50px'}
flexDirection="column"
>
{router.pathname === '/getting-started' || router.pathname === '/' ? (
<WaffleHeader />
) : null}
<ContentWrapper flexGrow={1}>{children}</ContentWrapper>
<Main mx="unset" width={'100%'}>
{router.pathname === '/getting-started' || router.pathname === '/' ? (
<WaffleHeader />
) : null}
<Flex
flexDirection={['column', 'column', 'row', 'row']}
maxWidth="98ch"
mx="auto"
flexGrow={1}
>
<ContentWrapper
width={
headings?.length
? ['100%', '100%', 'calc(100% - 200px)', 'calc(100% - 200px)']
: '100%'
}
mx="unset"
px="unset"
pt="unset"
css={css({
'& > *:not(pre):not(ul):not(ol)': {
px: space('extra-loose'),
},
'& > ul, & > ol': {
pr: space('extra-loose'),
pl: '64px ',
},
'& > pre > *:not(pre)': {
border: 'none',
px: space(['extra-loose', 'extra-loose', 'none', 'none']),
},
'& > pre': {
px: space(['none', 'none', 'extra-loose', 'extra-loose']),
border: 'none',
boxShadow: 'none',
},
})}
>
{children}
</ContentWrapper>
{headings?.length && headings.length > 1 ? (
<TableOfContents headings={headings} />
) : null}
</Flex>
</Main>
<Footer justifySelf="flex-end" />
</Main>
</Flex>
</Flex>
</>
</Flex>
);
};

View File

@@ -2,29 +2,29 @@ import { Box, Flex } from '@blockstack/ui';
import { Text } from '@components/typography';
import React from 'react';
import { Pagination } from './pagination';
import { Link } from './mdx-components';
import { Link } from '@components//mdx';
const Footer = ({ hidePagination, ...rest }: any) => {
return (
<>
{!hidePagination && <Pagination />}
<Flex
borderTop="1px solid"
borderColor="var(--colors-border)"
textStyle="body.small.medium"
color="ink.400"
p="base"
{...rest}
>
<Box>
<Text>Blockstack Design System</Text>
</Box>
<Box ml="auto">
<Link as="a" href="https://blockstack.org">
Blockstack PBC
</Link>
</Box>
</Flex>
{/*<Flex*/}
{/* borderTop="1px solid"*/}
{/* borderColor="var(--colors-border)"*/}
{/* textStyle="body.small.medium"*/}
{/* color="ink.400"*/}
{/* p="base"*/}
{/* {...rest}*/}
{/*>*/}
{/* <Box>*/}
{/* <Text>Blockstack Design System</Text>*/}
{/* </Box>*/}
{/* <Box ml="auto">*/}
{/* <Link as="a" href="https://blockstack.org">*/}
{/* Blockstack PBC*/}
{/* </Link>*/}
{/* </Box>*/}
{/*</Flex>*/}
</>
);
};

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Flex } from '@blockstack/ui';
import { Flex, Box, BlockstackIcon, color, space } from '@blockstack/ui';
import { Link, Text } from '@components/typography';
import MenuIcon from 'mdi-react/MenuIcon';
import CloseIcon from 'mdi-react/CloseIcon';
@@ -8,7 +8,7 @@ import { useMobileMenuState } from '@common/hooks/use-mobile-menu';
import { SideNav } from './side-nav';
import GithubIcon from 'mdi-react/GithubIcon';
import { IconButton } from '@components/icon-button';
import { color, space } from '@common/utils';
import { border } from '@common/utils';
const MenuButton = ({ ...rest }: any) => {
const { isOpen, handleOpen, handleClose } = useMobileMenuState();
@@ -26,17 +26,21 @@ const MenuButton = ({ ...rest }: any) => {
);
};
const GithubButton = ({ ...rest }: any) => {
return (
<IconButton
as="a"
href="https://github.com/blockstack/ux/tree/master/packages/ui#blockstack-ui"
target="_blank"
>
<GithubIcon size="20px" />
</IconButton>
);
};
const GithubButton = () => (
<IconButton
as="a"
href="https://github.com/blockstack/ux/tree/master/packages/ui#blockstack-ui"
target="_blank"
title="Find us on GitHub"
position="relative"
overflow="hidden"
>
<Text position="absolute" opacity={0} as="label">
Find us on GitHub
</Text>
<GithubIcon size="20px" />
</IconButton>
);
const Header = ({ ...rest }: any) => {
const { isOpen } = useMobileMenuState();
@@ -45,28 +49,36 @@ const Header = ({ ...rest }: any) => {
<>
<Flex
justifyContent="space-between"
borderBottom="1px solid"
borderColor="var(--colors-border)"
borderBottom={border()}
align="center"
px="base"
position="fixed"
width="100%"
bg="var(--colors-bg)"
bg={color('bg')}
zIndex={99}
height="50px"
boxShadow="mid"
>
<Text fontSize="14px">Blockstack Design System</Text>
<Flex align="center">
<Box color={color('invert')} mr={space('tight')}>
<BlockstackIcon size="20px" />
</Box>
<Box>
<Text color={color('invert')} fontSize="14px" fontWeight={600}>
Blockstack UI
</Text>
</Box>
</Flex>
<Flex align="center">
<Link
as="a"
mr={space('base')}
href="https://www.dropbox.com/work/Blockstack%20Branding"
href="https://www.dropbox.com/sh/5uyhon1dxax4t6t/AABnh34kFRzD2TSck1wE9fmqa?dl=0"
target="_blank"
fontSize="12px"
>
Branding Assets
</Link>
<ColorModeButton />
<GithubButton />
<MenuButton />
</Flex>
@@ -79,6 +91,7 @@ const Header = ({ ...rest }: any) => {
zIndex={99}
bg={color('bg')}
display={isOpen ? ['block', 'block', 'none'] : 'none'}
border="unset"
/>
</>
);

View File

@@ -1,6 +1,5 @@
import React, { forwardRef, Ref } from 'react';
import { Box } from '@blockstack/ui';
import { color } from '@components/color-modes';
import { Box, color } from '@blockstack/ui';
import { LinkProps } from '@components/typography';
export const IconButton = forwardRef((props: LinkProps, ref: Ref<HTMLDivElement>) => (

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { DocsLayout } from '@components/docs-layout';
import Head from 'next/head';
export default function Layout(frontMatter) {
const {
title,
description = 'The Blockstack design system, built with React and styled-system.',
headings,
} = frontMatter;
return ({ children }) => {
return (
<>
<Head>
<title>{title} | Blockstack UI</title>
<meta name="description" content={description} />
</Head>
<DocsLayout headings={headings}>{children}</DocsLayout>
</>
);
};
}

View File

@@ -1,8 +1,6 @@
import React from 'react';
import { Flex } from '@blockstack/ui';
import { Box, BoxProps } from '@blockstack/ui';
const Main = (props: any) => (
<Flex flexDirection="column" as="main" mx="auto" flexGrow={1} {...props} />
);
const Main = (props: BoxProps) => <Box mx="auto" as="main" flexGrow={1} {...props} />;
export { Main };

View File

@@ -1,218 +0,0 @@
import { Box, Flex, FlexProps, BoxProps } from '@blockstack/ui';
import NextLink from 'next/link';
import React, { forwardRef, Ref } from 'react';
import css from '@styled-system/css';
import CodeBlock from './code-block';
import LinkIcon from 'mdi-react/LinkVariantIcon';
import HashtagIcon from 'mdi-react/HashtagIcon';
import { useHover } from 'use-events';
import Head from 'next/head';
import { slugify } from '@common/utils';
import { useActiveHeading } from '@common/hooks/use-active-heading';
import { Text, Title, Pre } from '@components/typography';
const SmartLink = ({ href, ...rest }: { href: string }) => {
const isExternal = href.includes('http');
const link = <Link href={href} {...rest} />;
return isExternal ? (
link
) : (
<NextLink href={href} passHref>
{link}
</NextLink>
);
};
const Table = (props: any) => (
<Box
color="var(--colors-text-body)"
border="1px solic var(--colors-border)"
as="table"
textAlign="left"
mt="32px"
width="100%"
{...props}
/>
);
const THead = (props: any) => {
return (
<Box
as="th"
color="var(--colors-text-caption)"
bg="blue.50"
p={2}
textStyle={'body.small.medium'}
{...props}
/>
);
};
const TData = (props: any) => (
<Box
as="td"
p={2}
borderTopWidth="1px"
borderColor="var(--colors-border)"
textStyle="body.small"
whiteSpace="normal"
{...props}
/>
);
export const Link = forwardRef((props: { href: 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}
/>
));
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
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @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"
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
href={`#${props.id}`}
>
#
</Box>
)}
</Box>
</Text>
);
const Heading = ({ as, children, id, ...rest }: FlexProps) => {
// children should be a string, so we can get the slug of it to create anchors
const slug = slugify(children?.toString());
const [isActive, setActiveSlug] = useActiveHeading({ slug });
const [hovered, bind] = useHover();
return (
<>
<Flex key={slug} align="center" position="relative" {...bind} {...rest}>
{isActive ? (
<Box position="absolute" left="-20px" transform="translateY(1px)" color="blue.300">
<HashtagIcon size="1rem" />
</Box>
) : null}
<Box color={isActive ? 'blue' : 'ink'}>
<Title as={as}>{children}</Title>
</Box>
<Link
aria-label="anchor"
_hover={{ cursor: 'pointer', opacity: 1 }}
opacity={hovered ? 0.5 : 0}
px={2}
color="var(--colors-invert)"
as="a"
href={`#${slug}`}
onClick={() => setActiveSlug(slug)}
>
<LinkIcon size="1rem" />
</Link>
<Box id={slug} transform="translateY(-82px)" />
</Flex>
</>
);
};
const H1Title = ({ children, ...rest }: any) => (
<>
<Heading width="100%" as="h1" {...{ ...rest, children }} />
<Head>
<title>{children} - Blockstack UI</title>
</Head>
</>
);
const MDXComponents = {
h1: (props: any) => <H1Title {...props} />,
h2: (props: any) => <Heading width="100%" mt="extra-loose" as="h2" {...props} />,
h3: (props: any) => <Heading width="100%" mt="extra-loose" as="h3" {...props} />,
inlineCode: Pre,
code: CodeBlock,
pre: (props: any) => <Box as="pre" {...props} />,
br: (props: any) => <Box height="24px" {...props} />,
hr: (props: any) => (
<Box
as="hr"
borderTopWidth="1px"
borderColor="var(--colors-border)"
my="extra-loose"
c
{...props}
/>
),
table: Table,
th: THead,
td: TData,
a: (props: any) => <SmartLink {...props} />,
p: (props: any) => <Text as="p" mt="tight" display="block" lineHeight="24px" {...props} />,
ul: (props: any) => <Text as="ul" pt="base" pl="base" {...props} />,
ol: (props: any) => <Text as="ol" pt="base" pl="base" {...props} />,
li: (props: any) => <Box as="li" color="currentColor" pb="base" {...props} />,
blockquote: (props: any) => (
<Box
as="blockquote"
display="block"
my="base-loose"
border="1px solid"
borderColor="var(--colors-border)"
borderRadius="6px"
bg="var(--colors-bg-light)"
px={3}
py={3}
>
<Box
css={css({
'> *:first-of-type': {
marginTop: 0,
borderLeft: '4px solid',
borderColor: 'var(--colors-accent)',
pl: 2,
},
})}
{...props}
/>
</Box>
),
};
export { MDXComponents };

View File

@@ -0,0 +1,267 @@
import { Box, FlexProps, BoxProps, color, 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';
const preProps = {
display: 'inline-block',
border: border(),
borderRadius: '4px',
transform: 'translateY(-1px)',
fontSize: '12px',
padding: '2px 6px',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.04)',
bg: color('bg'),
};
export const InlineCode = (props: any) => <Text as="code" {...preProps} {...props} />;
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 {...rest}>
<Box
color={color('text-body')}
textAlign="left"
my={space('extra-loose')}
width="100%"
as="table"
>
{children}
</Box>
</Box>
);
export const THead = (props: any) => {
return (
<Box
as="th"
color="var(--colors-text-caption)"
bg="blue.50"
p={2}
textStyle={'body.small.medium'}
{...props}
/>
);
};
export const TData = (props: any) => (
<Box
as="td"
p={2}
borderTopWidth="1px"
borderColor="var(--colors-border)"
textStyle="body.small"
whiteSpace="normal"
{...props}
/>
);
export const Link = forwardRef((props: { href: 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
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @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"
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
href={`#${props.id}`}
>
#
</Box>
)}
</Box>
</Text>
);
const baseStyles = {
letterSpacing: '-0.01em',
fontFeatureSettings: `'ss01' on`,
};
const h1 = {
fontSize: '36px',
lineHeight: '52px',
fontWeight: 'bold',
};
const h2 = {
fontSize: '24px',
lineHeight: '40px',
fontWeight: 'bold',
};
const h3 = {
fontSize: '18px',
lineHeight: '32px',
fontWeight: 'bold',
};
const h4 = {
fontSize: '18px',
lineHeight: '32px',
};
const h5 = {
fontSize: '16px',
lineHeight: '28px',
fontWeight: 'bold',
};
const h6 = {
fontSize: '14px',
lineHeight: '28px',
fontWeight: 'bold',
};
const headings = {
h1,
h2,
h3,
h4,
h5,
h6,
};
const getHeadingStyles = (as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6') => {
return {
...baseStyles,
...headings[as],
};
};
const LinkButton = React.memo(({ link, onClick, ...rest }: BoxProps & { link: string }) => {
const { onCopy } = useClipboard(link);
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) => (
<Box
as="span"
display="block"
position="absolute"
style={{ userSelect: 'none', pointerEvents: 'none' }}
top="-120px"
id={id}
/>
);
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, setActiveSlug, url] = useActiveHeading(id);
const [isHovered, bind] = useHover();
const link = `#${id}`;
const handleLinkClick = () => {
setActiveSlug(id);
};
return (
<Title
as={as}
color={isActive ? color('accent') : color('text-title')}
display="flex"
style={{ alignItems: 'center' }}
position="relative"
{...bind}
{...getHeadingStyles(as as any)}
{...rest}
>
{children}
<AnchorOffset id={id} />
{isActive && <Hashtag />}
<LinkButton opacity={isHovered ? 1 : 0} onClick={handleLinkClick} link={link} />
</Title>
);
};

View File

@@ -0,0 +1,2 @@
export * from './mdx-components';
export * from './components';

View File

@@ -0,0 +1,93 @@
import React from 'react';
import { Box, space, BoxProps, color } from '@blockstack/ui';
import css from '@styled-system/css';
import CodeBlock from '../code-block';
import {
Heading,
Pre,
THead,
SmartLink,
TData,
Table,
InlineCode,
} from '@components/mdx/components';
import { Text } from '@components/typography';
import { border, onlyText, slugify } from '@common/utils';
const BaseHeading: React.FC<BoxProps> = React.memo(props => (
<Heading width="100%" mt={space('base-loose')} mb={space('tight')} {...props} />
));
const H1: React.FC<BoxProps> = props => <BaseHeading as="h1" mb={space('base-tight')} {...props} />;
const H2: React.FC<BoxProps> = props => <BaseHeading as="h2" {...props} />;
const H3: React.FC<BoxProps> = props => <BaseHeading as="h3" {...props} />;
const H4: React.FC<BoxProps> = props => <BaseHeading as="h4" {...props} />;
const H5: React.FC<BoxProps> = props => <BaseHeading as="h5" {...props} />;
const H6: React.FC<BoxProps> = props => <BaseHeading as="h6" {...props} />;
const Br: React.FC<BoxProps> = props => <Box height="24px" {...props} />;
const Hr: React.FC<BoxProps> = props => (
<Box
as="hr"
borderTopWidth="1px"
borderColor={color('border')}
my={space('extra-loose')}
{...props}
/>
);
const P: React.FC<BoxProps> = props => <Text display="block" as="p" {...props} />;
const Ol: React.FC<BoxProps> = props => (
<Box pt={space('base')} pl={space('base')} as="ol" {...props} />
);
const Ul: React.FC<BoxProps> = props => (
<Box pt={space('base')} pl={space('base-loose')} mb={space('base')} as="ul" {...props} />
);
const Li: React.FC<BoxProps> = props => <Box as="li" pb={space('tight')} {...props} />;
const BlockQuote: React.FC<BoxProps> = ({ children, ...rest }) => (
<Box display="block" mt={space('base-tight')} mb={space('base-loose')} {...rest}>
<Box
as="blockquote"
border="1px solid"
css={css({
border: border(),
borderRadius: 'md',
boxShadow: 'mid',
py: space('base-tight'),
px: space('base'),
bg: color('bg-light'),
'> *:first-of-type': {
marginTop: 0,
borderLeft: '4px solid',
borderColor: color('accent'),
pl: space('base'),
},
})}
>
{children}
</Box>
</Box>
);
export const MDXComponents = {
h1: H1,
h2: H2,
h3: H3,
h4: H4,
h5: H5,
h6: H6,
inlineCode: InlineCode,
code: CodeBlock,
pre: Pre,
br: Br,
hr: Hr,
table: Table,
th: THead,
td: TData,
a: SmartLink,
p: P,
ul: Ul,
ol: Ol,
li: Li,
blockquote: BlockQuote,
};

View File

@@ -1,13 +1,12 @@
import { Box, Flex } from '@blockstack/ui';
import { Box, Flex, FlexProps, color, space, BoxProps } from '@blockstack/ui';
import { Caption, Text } from '@components/typography';
import { useRouter } from 'next/router';
import React from 'react';
import { routes } from '@common/routes';
import { slugify, space } from '@common/utils';
import { routes, paginationRoutes } from '@common/routes';
import { slugify } from '@common/utils';
import Link from 'next/link';
import { useHover } from 'use-events';
import { ContentWrapper } from '@components/content-wrapper';
import { color } from '@components/color-modes';
const transition = 'all 0.2s cubic-bezier(0.23, 1, 0.32, 1)';
@@ -29,7 +28,7 @@ const Previous = ({ isHovered }: { isHovered: boolean }) => {
};
const Next = ({ isHovered }: { isHovered: boolean }) => {
return (
<Flex>
<Flex justifyContent="flex-end">
<Box>
<Caption>Next</Caption>
</Box>
@@ -44,13 +43,13 @@ const Next = ({ isHovered }: { isHovered: boolean }) => {
);
};
interface PaginationLinkProps {
interface PaginationLinkProps extends BoxProps {
slug: string;
label: string;
prev?: boolean;
}
const PaginationLink = ({ slug, label, prev }: PaginationLinkProps) => {
const PaginationLink = ({ slug, label, prev, ...rest }: PaginationLinkProps) => {
const [isHovered, bindHover] = useHover();
return (
<Link href={`/${slug}`} passHref>
@@ -60,57 +59,108 @@ const PaginationLink = ({ slug, label, prev }: PaginationLinkProps) => {
color: 'var(--colors-accent)',
}}
as="a"
textAlign="left"
py={space('extra-loose')}
display="block"
flexGrow={1}
{...bindHover}
{...rest}
>
<Caption display="block">
<Caption textAlign={prev ? 'left' : 'right'} display="block">
{prev ? <Previous isHovered={isHovered} /> : <Next isHovered={isHovered} />}
</Caption>
<Text display="block" textStyle="display.large" color="currentColor">
<Text
textAlign={prev ? 'left' : 'right'}
display="block"
textStyle="display.large"
color="currentColor"
>
{label}
</Text>
</Box>
</Link>
);
};
const Pagination = () => {
const router = useRouter();
const routesAsSlugs = routes.map(r => slugify(r));
const _route = router.asPath.replace('/', '');
const section = _route.includes('/') ? _route.split('/')[0] : undefined;
const __route = _route.includes('/') ? _route.split('/')[1] : _route;
const route = __route === '' ? 'getting-started' : __route;
const index = routesAsSlugs.indexOf(route);
const previous = routes[index - 1];
const previousSlug = section
? section + '/' + routesAsSlugs[index - 1]
: routesAsSlugs[index - 1];
const next = routes[index + 1];
const nextSlug = section ? section + '/' + routesAsSlugs[index + 1] : routesAsSlugs[index + 1];
return (
<ContentWrapper
borderTop="1px solid"
borderColor={color('border')}
flexDirection="row"
alignItems="baseline"
justify="space-between"
pt="unset"
pb="unset"
>
{previous && (
<Box textAlign="left">
<PaginationLink slug={previousSlug} label={previous} prev />
</Box>
)}
{next && (
<Box textAlign="right" ml={!previous ? 'auto' : undefined}>
<PaginationLink slug={nextSlug} label={next} />
</Box>
)}
</ContentWrapper>
);
const getRouteWithSection = (route: string) => {
let section = '';
const keys = Object.keys(paginationRoutes);
keys.forEach(key => {
const routes = paginationRoutes[key].map(r => slugify(r));
if (routes.length && routes.find(r => r === route) && key !== 'top' && key !== 'bottom') {
section = key + '/';
}
});
return section + route;
};
export { Pagination };
const usePagination = () => {
const router = useRouter();
const [state, setState] = React.useState({
previous: undefined,
previousSlug: undefined,
next: undefined,
nextSlug: undefined,
});
React.useEffect(() => {
const routesAsSlugs = routes.map(r => slugify(r));
const _route = router.asPath.replace('/', '');
const __route = _route.includes('/') ? _route.split('/')[1] : _route;
const route = __route === '' ? 'getting-started' : __route;
const index = routesAsSlugs.indexOf(route);
const previous = routes[index - 1];
const previousSlug = getRouteWithSection(routesAsSlugs[index - 1]);
const next = routes[index + 1];
const nextSlug = getRouteWithSection(routesAsSlugs[index + 1]);
setState({
previous,
previousSlug,
next,
nextSlug,
});
}, [router.asPath]);
const { previous, previousSlug, next, nextSlug } = state;
return [
{ label: previous, path: previousSlug, condition: !!previous },
{
label: next,
path: nextSlug,
condition: !!next,
},
];
};
export const Pagination: React.FC<FlexProps> = props => {
const buttons = usePagination();
return (
<Box maxWidth="100%" {...props}>
<ContentWrapper
borderTop="1px solid"
borderColor={color('border')}
flexDirection="row"
alignItems="baseline"
justify="space-between"
pt="unset"
pb="unset"
width="100%"
maxWidth="98ch"
mx="auto"
px={space('extra-loose')}
>
{buttons.map((button, index) =>
button.condition ? (
<PaginationLink
textAlign={index === 0 ? 'left' : 'right'}
slug={button.path}
label={button.label}
prev={index === 0}
key={index}
/>
) : null
)}
</ContentWrapper>
</Box>
);
};

View File

@@ -0,0 +1,85 @@
import * as React from 'react';
import Router from 'next/router';
import NProgress from 'nprogress';
import { createGlobalStyle, css } from 'styled-components';
const styles = css`
/* Make clicks pass-through */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: var(--colors-invert);
position: fixed;
z-index: 999999999;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
/* Fancy blur effect */
#nprogress .peg {
display: none;
}
/* Remove these to get rid of the spinner */
#nprogress .spinner {
display: none;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`;
const ProgressBarStyles = createGlobalStyle`${styles}`;
export const useProgressBar = () => {
const { start, done } = NProgress;
return {
start,
done,
};
};
export const ProgressBar = () => {
React.useEffect(() => {
Router.events.on('routeChangeStart', url => {
NProgress.start();
});
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());
}, []);
return (
<>
<ProgressBarStyles />
</>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Box } from '@blockstack/ui';
import { slugify } from '@common/utils';
import { Box, color, space } from '@blockstack/ui';
import { border, slugify } from '@common/utils';
import { Text, Caption } from '@components/typography';
import Link from 'next/link';
import { useRouter } from 'next/router';
@@ -14,7 +14,7 @@ const sections = [
{ links: bottomNavLinks },
];
const Wrapper = ({ width = '240px', children, ...rest }: any) => (
const Wrapper = ({ width = '200px', children, ...rest }: any) => (
<Box
position="relative"
width={width}
@@ -27,12 +27,11 @@ const Wrapper = ({ width = '240px', children, ...rest }: any) => (
>
<Box
position="fixed"
borderRight={['unset', '1px solid', '1px solid']}
borderColor={['unset', 'var(--colors-border)', 'var(--colors-border)']}
top={50}
width={width}
height="calc(100vh - 50px)"
overflow="auto"
borderRight={['none', border(), border()]}
>
{children}
</Box>
@@ -51,9 +50,10 @@ const LinkItem = React.forwardRef(({ isActive, ...rest }: any, ref) => (
}
: null
}
textStyle="body.small"
color={isActive ? 'var(--colors-text-title)' : 'var(--colors-text-body)'}
color={isActive ? color('accent') : color('text-body')}
fontWeight={isActive ? 'semibold' : 'normal'}
fontSize="14px"
lineHeight="18px"
as="a"
{...rest}
/>
@@ -68,9 +68,9 @@ const Links = ({ links, prefix = '', ...rest }: any) => {
const isActive =
router.pathname.includes(slug) || (router.pathname === '/' && slug === 'getting-started');
return (
<Box px="base" py="extra-tight" key={linkKey} onClick={handleClose} {...rest}>
<Link href={`/${prefix + slug}`}>
<LinkItem isActive={isActive} href={`/${prefix + slug}`}>
<Box width="100%" px="base" py="1px" key={linkKey} onClick={handleClose} {...rest}>
<Link href={`/${prefix + slug}`} passHref>
<LinkItem width="100%" isActive={isActive} href={`/${prefix + slug}`}>
{link}
</LinkItem>
</Link>
@@ -80,26 +80,15 @@ const Links = ({ links, prefix = '', ...rest }: any) => {
};
const SectionTitle = ({ children, textStyles, ...rest }: any) => (
<Box px={4} pb="tight" {...rest}>
<Caption
fontWeight="600"
fontSize="10px"
letterSpacing="1px"
textTransform="uppercase"
{...textStyles}
>
<Box px={space('base')} pb={space('extra-tight')} {...rest}>
<Caption fontSize="14px" fontWeight="600" color={color('text-title')} {...textStyles}>
{children}
</Caption>
</Box>
);
const Section = ({ section, isLast, ...rest }: any) => (
<Box
borderBottom="1px solid"
borderColor={isLast ? 'transparent' : 'var(--colors-border)'}
py="base"
{...rest}
>
<Box width="100%" pt={space('base')} {...rest}>
{section.title ? <SectionTitle>{section.title}</SectionTitle> : null}
<Links prefix={section.prefix} links={section.links} />
</Box>

View File

@@ -0,0 +1,41 @@
import * as React from 'react';
import { color } from '@blockstack/ui';
import { useTooltip, TooltipPopup } from '@reach/tooltip';
const centered = (triggerRect: any, tooltipRect: any) => {
if (typeof window === 'undefined') return { left: 0, top: 0 };
const triggerCenter = triggerRect.left + triggerRect.width / 2;
const left = triggerCenter - tooltipRect.width / 2;
const maxLeft = window.innerWidth - tooltipRect.width - 2;
return {
left: Math.min(Math.max(2, left), maxLeft) + window.scrollX,
top: triggerRect.bottom + 8 + window.scrollY,
};
};
export const Tooltip = ({ children, label, 'aria-label': ariaLabel }: any) => {
const [trigger, tooltip] = useTooltip();
const { onMouseDown, ...triggerProps } = trigger;
return (
<>
{React.cloneElement(children, triggerProps)}
<TooltipPopup
{...tooltip}
label={label}
aria-label={ariaLabel}
style={{
position: 'absolute',
zIndex: 99999,
background: color('invert'),
color: color('bg'),
border: 'none',
borderRadius: '3px',
padding: '0.5em 1em',
fontSize: '12px',
}}
position={centered}
/>
</>
);
};

View File

@@ -1,6 +1,5 @@
import * as React from 'react';
import { Text as BaseText, BoxProps } from '@blockstack/ui';
import { color } from '@components/color-modes';
import { Text as BaseText, BoxProps, color } from '@blockstack/ui';
import { border } from '@common/utils';
export const Text = React.forwardRef((props: BoxProps, ref) => (

View File

@@ -1,44 +0,0 @@
import React from 'react';
import { View, PropTypes } from 'react-view';
import { Button } from '@blockstack/ui';
export const ButtonView = () => (
<View
componentName="Button"
props={{
children: {
value: 'Hello world',
type: PropTypes.ReactNode,
description: 'Visible label.',
},
loadingText: {
value: 'Loading...',
type: PropTypes.String,
description: 'The message to be shown when the button is loading.',
},
onClick: {
value: '() => alert("click")',
type: PropTypes.Function,
description: 'Function called when button is clicked.',
},
isDisabled: {
value: false,
type: PropTypes.Boolean,
description: 'Indicates that the button is disabled',
},
isLoading: {
value: false,
type: PropTypes.Boolean,
description: 'Enables the loading state of the button.',
},
}}
scope={{
Button,
}}
imports={{
'@blockstack/ui': {
named: ['Button'],
},
}}
/>
);

View File

@@ -1 +0,0 @@
export * from './button';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Flex } from '@blockstack/ui';
import { Flex, space } from '@blockstack/ui';
import { Text } from '@components/typography';
import { ContentWrapper } from './content-wrapper';
import { useAppState } from '@common/hooks/use-app-state';
@@ -13,9 +13,10 @@ export const WaffleHeader = () => {
minHeight={['unset', 'unset', '300px']}
bg="ink"
borderBottom="1px solid var(--colors-border)"
pb={space('extra-loose')}
>
<ContentWrapper>
<Text color="white" as="h1" display="block" fontSize={[5, 5, 7]}>
<ContentWrapper maxWidth="98ch" width="100%" px={space('extra-loose')}>
<Text color="white" as="h1" display="block" fontSize="42px">
Blockstack UI
</Text>
<Text pt={1} display="block" as="h2" fontSize={[3, 3, 4]} color="white">

View File

@@ -1,10 +1,11 @@
import * as React from 'react';
import { Flex, Box, Stack, FlexProps } from '@blockstack/ui';
import { Flex, Box, Stack, FlexProps, color, space } from '@blockstack/ui';
import { ContentWrapper } from '@components/content-wrapper';
import { Text, Title, Link } from '@components/typography';
import { color } from '@components/color-modes';
import FileDocumentEditOutlineIcon from 'mdi-react/FileDocumentEditOutlineIcon';
import { border, space } from '@common/utils';
import { border } from '@common/utils';
import { DocsLayout } from '@components/docs-layout';
import Head from 'next/head';
const toPx = (number: number): string => `${number}px`;
@@ -44,25 +45,37 @@ const Graphic = (props: FlexProps) => {
const NotFoundPage = () => {
return (
<ContentWrapper align="center" justify="center" flexGrow={1}>
<Stack spacing={space('loose')} pb={space('100px')}>
<Box>
<Graphic mb={space('base')} />
<Box textAlign="center">
<Title as="h1">We're still working on this</Title>
<DocsLayout>
<Head>
<title>Page not found | Blockstack UI</title>
</Head>
<ContentWrapper align="center" justify="center" flexGrow={1}>
<Stack spacing={space('loose')} pb={space('100px')}>
<Box>
<Graphic mb={space('base')} />
<Box textAlign="center">
<Title as="h1">We're still working on this</Title>
</Box>
</Box>
</Box>
<Box mx="auto" maxWidth="40ch" textAlign="center">
<Text>
The docs for the Blockstack UI library are still pretty new, looks like there's nothing
here yet.
</Text>
<Box mt={space('base')}>
<Link color={color('accent')}>Want to contribute?</Link>
<Box mx="auto" maxWidth="40ch" textAlign="center">
<Text>
The docs for the Blockstack UI library are still pretty new, looks like there's
nothing here yet.
</Text>
<Box mt={space('base')}>
<Link
as="a"
color={color('accent')}
href="https://github.com/blockstack/ux"
target="_blank"
>
Want to contribute?
</Link>
</Box>
</Box>
</Box>
</Stack>
</ContentWrapper>
</Stack>
</ContentWrapper>
</DocsLayout>
);
};

View File

@@ -1,34 +1,45 @@
import React from 'react';
import App, { AppContext } from 'next/app';
import { CSSReset, ThemeProvider, theme } from '@blockstack/ui';
import { CSSReset, ThemeProvider, theme, ColorModeProvider } from '@blockstack/ui';
import { MDXProvider } from '@mdx-js/react';
import { MDXComponents } from '@components/mdx-components';
import { DocsLayout } from '@components/docs-layout';
import { MDXComponents } from '@components/mdx';
import { AppStateProvider } from '@components/app-state';
import { ColorModeProvider } from '@components/color-modes';
import { parseCookies } from 'nookies';
import { MdxOverrides } from '@components/docs-layout';
import { ProgressBar } from '@components/progress-bar';
import engine from 'store/src/store-engine';
import cookieStorage from 'store/storages/cookieStorage';
import 'typeface-inter';
import 'typeface-fira-code';
const AppWrapper = ({ children, version }: any) => (
const COLOR_MODE_COOKIE = 'color_mode';
const cookieSetter = engine.createStore([cookieStorage]);
const handleColorModeChange = (mode: string) => cookieSetter.set(COLOR_MODE_COOKIE, mode);
const AppWrapper = ({ children, colorMode = 'light', version }: any) => (
<ThemeProvider theme={theme}>
<ColorModeProvider>
<MdxOverrides />
<ColorModeProvider onChange={handleColorModeChange} colorMode={colorMode}>
<ProgressBar />
<MDXProvider components={MDXComponents}>
<AppStateProvider version={version}>
<DocsLayout>
<CSSReset />
{children}
</DocsLayout>
<CSSReset />
{children}
</AppStateProvider>
</MDXProvider>
</ColorModeProvider>
</ThemeProvider>
);
const MyApp = ({ Component, pageProps, ...rest }: any) => {
const MyApp = ({ Component, pageProps, colorMode, ...rest }: any) => {
const [version, setVersion] = React.useState(pageProps?.version);
if (!version && pageProps?.version) {
setVersion(pageProps?.version);
}
return (
<AppWrapper version={version}>
<AppWrapper colorMode={colorMode} version={version}>
<Component {...pageProps} />
</AppWrapper>
);
@@ -36,6 +47,12 @@ const MyApp = ({ Component, pageProps, ...rest }: any) => {
MyApp.getInitialProps = async (appContext: AppContext) => {
const appProps = await App.getInitialProps(appContext);
const cookies = parseCookies(appContext.ctx);
let colorMode = undefined;
if (cookies) {
colorMode = cookies[COLOR_MODE_COOKIE] ? JSON.parse(cookies[COLOR_MODE_COOKIE]) : undefined;
}
if (appContext.ctx.res) {
try {
const res = await fetch('https://registry.npmjs.org/@blockstack/ui');
@@ -47,13 +64,14 @@ MyApp.getInitialProps = async (appContext: AppContext) => {
...appProps.pageProps,
version,
},
colorMode,
};
} catch (e) {
console.log(e);
}
}
return appProps;
return { ...appProps, colorMode };
};
export default MyApp;

View File

@@ -1,4 +1,4 @@
import Document, { DocumentContext } from 'next/document';
import Document, { DocumentContext, Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
@@ -27,4 +27,16 @@ export default class MyDocument extends Document<any> {
sheet.seal();
}
}
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

View File

@@ -1,8 +1,12 @@
---
title: 'Getting started'
---
# Getting Started
First we need to install `@blockstack/ui` and its various dependencies.
```bash
```
yarn add @blockstack/ui styled-components babel-plugin-styled-components
```
@@ -44,7 +48,7 @@ const App({ children }) => {
}
```
## Contributing
### Contributing
Please see our [contribution guidelines](/contributing) to learn how you can
contribute to this project.

View File

@@ -1,9 +1,13 @@
---
title: 'Box'
---
# Box
Box is the base primitive component on top of which all other components
are built. By default, it renders a `div` element. When building any new
layout elements or components, you'll almost always go for a `Box` to
start with. There are some other components that are lumped into the
start with. There are some other components that are in the same
category of "primitives": `Flex`, `Grid`, `Text`.
## Import
@@ -15,9 +19,9 @@ import { Box } from '@blockstack/ui';
## Usage
```jsx
<Box w="100%" p={4} color="white" bg="salmon">
<Box w="100%" p={space('base')} color="white" bg="salmon">
Box is the base element all others are created from.{' '}
<Box as="span" bg="white" color="ink.900">
<Box as="span" bg="white" color={themeColor('ink.900')}>
It can also be inline.
</Box>
</Box>
@@ -29,19 +33,25 @@ You can use the `as` prop to change which element is rendered. You can pass a st
```jsx
<>
<Box as="button" rounded="md" bg="salmon" color="white" px={4} h={8}>
{/* Box as a button! */}
<Box as="button" rounded="md" bg="salmon" color="white" px={space('base')} height={12}>
Button
</Box>
{/* Box as Flex for recreating a button */}
<Box
mt={5}
as={Flex}
align="center"
justify="center"
rounded="md"
bg="salmon"
bg={themeColor('blue')}
_hover={{
bg: themeColor('blue.hover'),
cursor: 'pointer',
}}
color="white"
px={4}
h={8}
px={space('base')}
mt={space('base')}
height="64px"
as={Flex}
alignItems="center"
justifyContent="center"
>
Button
</Box>

View File

@@ -1,4 +1,6 @@
import { ButtonView } from '../../components/views';
---
title: 'Button'
---
# Button
@@ -22,7 +24,7 @@ import { Button } from '@blockstack/ui';
Use the `size` prop to change the size of the button. You can set the value to `sm`, `md`, or `lg`. Default value is `md`.
```jsx
<ButtonGroup spacing={4}>
<ButtonGroup spacing={space('base')}>
<Button size="sm">Button</Button>
<Button size="md">Button</Button>
<Button size="lg">Button</Button>
@@ -35,7 +37,7 @@ Use the `mode` prop to change the visual style of the Button. You can set the
value to `primary`, `secondary`, or `tertiary`.
```jsx
<ButtonGroup spacing={4}>
<ButtonGroup spacing={space('base')}>
<Button mode="primary">Button</Button>
<Button mode="secondary">Button</Button>
<Button mode="tertiary">Button</Button>
@@ -50,7 +52,7 @@ spinner and the loading text. Otherwise, the button will take the width of the
text label and show only the spinner
```jsx
<ButtonGroup spacing={4}>
<ButtonGroup spacing={space('base')}>
<Button isLoading variant="solid">
Email
</Button>
@@ -67,15 +69,15 @@ text label and show only the spinner
## Composition
Button composes [PseudoBox](/pseudobox) and all props you pass (variant,
Button composes [Box](/box) and all props you pass (mode,
bg, color, etc.) are converted to style props. This means you can override
any style of the Button via props.
```jsx
// The size prop affects the height of the button
// but I can still override it by passing a custom height
<Button size="md" height="48px" width="200px" border="2px" borderColor="green">
Button
<Button size="md" height="200px" width="200px">
Square Button
</Button>
```
@@ -117,10 +119,6 @@ style props to style the button.
</Box>
```
## Experiment
<ButtonView />
## Props
The Button composes the `Box` component so you can pass props for

View File

@@ -0,0 +1,117 @@
---
title: 'Color modes'
---
# Color modes
This component is used to enable applications to style our components in different modes: light and dark.
> Note: many of our components have yet to be converted to use these color names.
## ColorModeProvider
To use, import the `ColorModeProvider` into the root of your application (such as `_app.tsx` for next.js applications,
or an App component for other react applications.) This component provides the context for our color mode to be
consumed within the application.
```tsx live=false
import { ColorModeProvider } from '@blockstack/ui';
// _app.tsx
const AppWrapper = ({ children, colorMode = 'light' }: AppWrapperProps => (
<ThemeProvider>
<CSSReset />
<ColorModeProvider onChange={handleColorModeChange} colorMode={colorMode}>
{children}
</ColorModeProvider>
</ThemeProvider>
);
```
### Provider Props
ColorModeProvider takes only a few props:
- `colorMode?: 'light' | 'dark'`
- `onChange?: (mode: string) => void`
### colorMode
Useful for setting a default value, or a passing a persisted set value to default to.
### onChange
This method is called when the the color mode is toggled via the `toggleColorMode` function that you can access via the
`useColorMode` hook. Here is an example to illustrate how one might persist the value to a cookie so that it can be
used in a server side rendered environment.
```tsx live=false
import { ColorModeProvider } from '@blockstack/ui';
import engine from 'store/src/store-engine';
import cookieStorage from 'store/storages/cookieStorage';
// _app.tsx
const COLOR_MODE_COOKIE = 'color_mode';
const cookieSetter = engine.createStore([cookieStorage]);
const handleColorModeChange = (mode: string) => cookieSetter.set(COLOR_MODE_COOKIE, mode);
const AppWrapper = ({ children, colorMode = 'light' }: AppWrapperProps => (
<ThemeProvider>
<CSSReset />
<ColorModeProvider onChange={handleColorModeChange} colorMode={colorMode}>
{children}
</ColorModeProvider>
</ThemeProvider>
);
```
## useColorMode
There is a color mode hook exposed that can be used to access the current `colorMode` value and a method to toggle the color mode.
```tsx live=false
import { useColorMode } from '@blockstack/ui';
export const ColorModeButton = forwardRef((props: LinkProps, ref: Ref<HTMLDivElement>) => {
const { colorMode, toggleColorMode } = useColorMode();
return (
<IconButton onClick={toggleColorMode} title="Toggle color mode" {...props} ref={ref}>
{colorMode === 'dark' ? <DarkModeIcon /> : <LightModeIcon />}
</IconButton>
);
});
);
```
## Colors
With the color modes component, we have a new way of naming our colors. Rather than them reflecting the name of each color, we now are using more semantically meaningful names. With this, we have exposed a helper function called `color` that allows you to pass in a name for one of the colors. This is useful because with typescript, it allows for autocomplete functionality and the ability to ensure strict typechecking on something like a color name.
```tsx live=false
import { color } from '@blockstack/ui';
// some-component.tsx
<Box background={color('bg-alt')} />;
```
Here are all of the type literals:
```tsx live=false
export type ColorsStringLiteral =
| 'accent'
| 'bg'
| 'bg-alt'
| 'bg-light'
| 'invert'
| 'text-hover'
| 'text-title'
| 'text-caption'
| 'text-body'
| 'input-placeholder'
| 'border'
| 'feedback-alert'
| 'feedback-error'
| 'feedback-success';
```

View File

@@ -1,3 +1,7 @@
---
title: 'Flex'
---
# Flex
Flex is [Box](/box) with `display: flex` and comes with helpful style shorthand. It

View File

@@ -1,3 +1,7 @@
---
title: 'Grid'
---
# Grid
A primitive useful for grid layouts. Grid is `Box` with `display: grid` and

View File

@@ -1,3 +1,7 @@
---
title: 'Input'
---
# Input
Input component enables the user to interact with and input content and data.

View File

@@ -1,315 +0,0 @@
# PseudoBox
This component is inspired by
[Tailwind CSS](https://tailwindcss.com/docs/pseudo-class-variants) Pseudo-Class
variants and [Glamorous Pseudo](https://github.com/tkh44/glamorous-pseudo/).
PseudoBox composes [Box](/box) component and provides props to style common CSS pseudo
selectors.
Styling elements on hover, focus, and more can be accomplished by prefixing `_`
with the appropriate pseudo selector.
For example, to style `&:hover`, use the `_hover` prop and pass the style props.
We use the underscore `_` notation to visually separate pseudo style props from
regular style props.
## Import
```js
import { PseudoBox } from '@blockstack/ui';
```
## Usage
```jsx
<Flex as="form">
<PseudoBox
as="input"
placeholder="Your email"
type="email"
flex="1"
py={2}
px={4}
rounded="md"
bg="gray.100"
borderWidth="1px"
_hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
_focus={{
outline: 'none',
bg: 'white',
boxShadow: 'outline',
borderColor: 'gray.300',
}}
/>
<PseudoBox
as="button"
bg="teal.500"
py={2}
px={4}
ml={3}
rounded="md"
fontWeight="semibold"
color="white"
_hover={{ bg: 'teal.600' }}
_focus={{ boxShadow: 'outline' }}
>
Sign Up
</PseudoBox>
</Flex>
```
PseudoBox includes first-class support for styling elements on hover, focus,
active, disabled, visited, first-child, last-child, odd-child, even-child,
focus-within, and more.
It was created to help reduce the need to pass `css` prop or use `styled(...)`
function to style common pseudo states.
### Hover
Add the `_hover` prop to only apply style props on hover.
```jsx
<PseudoBox
as="button"
color="blue.700"
fontWeight="semibold"
py={2}
px={4}
borderWidth="1px"
borderColor="blue"
rounded="md"
_hover={{ bg: 'blue', color: ' white' }}
_focus={{ boxShadow: 'high' }}
>
Hover me
</PseudoBox>
```
### Focus
Add the `_focus` prop to only apply a styles on focus.
```jsx
<PseudoBox
as="input"
placeholder="Focus me"
py={2}
px={4}
bg="white"
color="ink"
borderColor="ink.100"
borderWidth="2px"
rounded="md"
_focus={{ bg: 'white', borderColor: 'blue' }}
/>
```
### Active
Add the `_active` prop to only apply a styles on active.
```jsx
<PseudoBox
as="button"
fontWeight="semibold"
py={2}
px={4}
rounded="md"
color="white"
bg="blue"
_active={{ bg: 'green' }}
_focus={{ boxShadow: 'outline' }}
>
Click me
</PseudoBox>
```
### Disabled
Add the `_disabled` prop to style an element when it is disabled. This style
will apply when the `disabled` or `aria-disabled` attribute of an element is set
to `true`
```jsx
<Stack isInline>
<PseudoBox
as="button"
fontWeight="semibold"
py={2}
px={4}
rounded="md"
color="white"
bg="blue.500"
_active={{ bg: 'blue.700' }}
_focus={{ boxShadow: 'outline' }}
>
Click me
</PseudoBox>
<PseudoBox
as="button"
disabled
fontWeight="semibold"
py={2}
px={4}
rounded="md"
color="white"
bg="blue.500"
_active={{ bg: 'blue.700' }}
_focus={{ boxShadow: 'outline' }}
_disabled={{ opacity: 0.6 }}
>
Click me
</PseudoBox>
</Stack>
```
### Visited
Add the `_visited` props to style a link that has been visited.
```jsx
<Stack isInline>
<PseudoBox as="a" href="/radio" color="blue.600" textDecoration="underline" fontWeight="semibold">
Unvisited Link
</PseudoBox>
<PseudoBox
as="a"
href="/pseudobox"
color="blue.600"
textDecoration="underline"
fontWeight="semibold"
_visited={{ color: 'purple.600' }}
>
Visited Link
</PseudoBox>
</Stack>
```
### First Child
Add the `_first` prefix to style an element when it is the first-child of its
parent. This is mostly useful when elements are being generated in some kind of
loop.
```jsx
<Box borderWidth="1px" rounded="md">
{['One', 'Two', 'Three'].map(item => (
<PseudoBox key={item} px={4} py={2} borderTopWidth="1px" _first={{ borderTopWidth: 0 }}>
{item}
</PseudoBox>
))}
</Box>
```
### Last Child
Add the `_last` prefix to style an element when it is the last-child of its
parent. This is mostly useful when elements are being generated in some kind of
loop.
```jsx
<Box borderWidth="1px" rounded="md">
{['One', 'Two', 'Three'].map(item => (
<PseudoBox key={item} px={4} py={2} borderBottomWidth="1px" _last={{ borderBottomWidth: 0 }}>
{item}
</PseudoBox>
))}
</Box>
```
### Odd Child
Add the `_odd` prop to style an element when it is the odd-child of its parent.
This is mostly useful when elements are being generated in some kind of loop.
```jsx
<Box borderWidth="1px" rounded="md" overflow="hidden">
{['One', 'Two', 'Three'].map(item => (
<PseudoBox key={item} px={4} py={2} bg="white" _odd={{ bg: 'gray.100' }}>
{item}
</PseudoBox>
))}
</Box>
```
### Even Child
Add the `_even` prop to style an element when it is the even-child of its
parent. This is mostly useful when elements are being generated in some kind of
loop.
```jsx
<Box borderWidth="1px" rounded="md" overflow="hidden">
{['One', 'Two', 'Three'].map(item => (
<PseudoBox key={item} px={4} py={2} bg="white" _even={{ bg: 'gray.100' }}>
{item}
</PseudoBox>
))}
</Box>
```
### Group Hover
If you need to style a child element when hovering over a specific parent
element, add the `role="group"` attribute to the parent element, then you can
use `_groupHover` prop to style the child element.
```jsx
<PseudoBox
role="group"
maxW="sm"
overflow="hidden"
rounded="md"
p={5}
cursor="pointer"
bg="white"
boxShadow="md"
_hover={{ bg: 'blue.500' }}
>
<PseudoBox
fontWeight="semibold"
fontSize="lg"
mb={1}
color="gray.900"
_groupHover={{ color: 'white' }}
>
New Project
</PseudoBox>
<PseudoBox color="gray.700" mb={2} _groupHover={{ color: 'white' }}>
Create a new project from a variety of starting templates.
</PseudoBox>
</PseudoBox>
```
## Selectors and Props
PseudoBox can be used to style any interactive component. You can apply styles
to the following selectors. The selectors are also ARIA-friendly to help you
naturally use `aria` attributes for better accessibility.
All PseudoBox props can use the style props.
[Learn more about Style props](/style-props)
| Selector | Prop |
| -------------------------------------- | -------------- |
| `&:hover` | `_hover` |
| `&:active` | `_active` |
| `&:focus` | `_focus` |
| `&:before` | `_before` |
| `&:after` | `_after` |
| `&::placeholder` | `_placeholder` |
| `&:first-of-type` | `_first` |
| `&:last-of-type` | `_last` |
| `[role=group]:hover &` | `_groupHover` |
| `&:disabled`, `[aria-disabled=true]` | `_disabled` |
| `&[readonly]`, `&[aria-readonly=true]` | `_readonly` |
| `&[aria-checked=true]` | `_checked` |
| `&[aria-selected=true]` | `_selected` |
| `&[aria-expanded=true]` | `_expanded` |
| `&[aria-invalid=true]` | `_invalid` |
| `&[aria-pressed=true]` | `_pressed` |
| `&[aria-invalid=true]` | `_invalid` |
| `&[aria-grabbed=true]` | `_grabbed` |

View File

@@ -1,3 +1,7 @@
---
title: 'Stack'
---
# Stack
Stack is a layout utility component that makes it easy to stack elements

View File

@@ -1,3 +1,7 @@
---
title: 'Text'
---
import { TextDisplay } from '@components/text-display';
# Text

View File

@@ -1,3 +1,7 @@
---
title: 'Contributing'
---
# Contributing to Blockstack UI
First of all: thank you for being interesting in helping the project out!

View File

@@ -1,3 +1,7 @@
---
title: 'Further reading'
---
# Further reading
As with most things, much of what we build is supported by the work of many other people. This page lists out some of the writing that has influenced the thought and ideas behind this design system.
@@ -18,6 +22,6 @@ As with most things, much of what we build is supported by the work of many othe
[What is a Design System?](https://varun.ca/what-is-a-design-system/)<br />
[Tailwind CSS](https://tailwindcss.com/)<br />
[Styled Components](https://styled-components.com/)<br />
[Styled System](https://styled-system.com/)
[Primer Components](https://primer.style/components)
[Styled System](https://styled-system.com/)<br />
[Primer Components](https://primer.style/components)<br />
[Chakra UI](https://chakra-ui.com)

View File

@@ -1,3 +1,7 @@
---
title: 'useTheme | Hooks'
---
# useTheme
`useTheme` is a custom hook to get the theme object from context.

View File

@@ -1,6 +1,12 @@
---
title: 'Patterns and principles'
---
# Patterns and principles
Blockstack UI is built with a few things in mind: consistency, composability, easy of use, and extensibility. These components aren't meant to be highly complex instances of specific UI elements; rather they are meant to be primitive components that you can use to build anything you want.
Blockstack UI is built with a few things in mind: consistency, composability, easy of use, and extensibility.
These components aren't meant to be highly complex instances of specific UI elements; rather they are meant to be
primitive components that you can use to build anything you want.
## System props

View File

@@ -1,4 +1,8 @@
# Responsive Styles
---
title: 'Responsive styles'
---
# Responsive styles
Blockstack UI supports responsive styles out of the box because it is built with styled-system. Instead of manually adding
`@media` queries and adding nested styles throughout your code, any component allows

View File

@@ -1,10 +1,17 @@
---
title: 'System props'
---
# System props
All of the components in Blockstack UI are built with `styled-components` and implement `styled-system`. With this, you can pass css attributes to any component as props. Check out the page [further reading](/further-reading) to dig deeper.
All of the components in Blockstack UI are built with `styled-components` and implement `styled-system`. With this,
you can pass css attributes to any component as props. Check out the page [further reading](/further-reading) to dig
deeper.
## System props reference
See below for the various props and aliases you can pass for styling components. You can see the full spec on the [Styled System docs](https://styled-system.com/api).
See below for the various props and aliases you can pass for styling components. You can see the full spec on the
[Styled System docs](https://styled-system.com/api).
### Margin & padding

View File

@@ -1,3 +1,7 @@
---
title: 'Theme'
---
import { Box, Flex, Grid, useTheme } from '@blockstack/ui';
import { ColorPalette, ColorPalettes, ColorWrapper, Colors } from '@components/color-palette';

View File

@@ -17,9 +17,7 @@
"paths": {
"@components/*": ["components/*"],
"@pages/*": ["pages/*"],
"@common/*": ["common/*"],
"@blockstack/ui": ["../ui"],
"@blockstack/ui/*": ["../ui/*"]
"@common/*": ["common/*"]
},
"baseUrl": "src"
},

View File

@@ -1,4 +1,4 @@
import { Theme } from '@blockstack/ui';
import { Theme } from '../theme';
import {
ColorModesInterface,
ColorsStringLiteral,

1186
yarn.lock

File diff suppressed because it is too large Load Diff