From 6050c0c3ccffb39d61b70e8a6464ac1995088dfe Mon Sep 17 00:00:00 2001 From: Luke Peavey <8286271+lukePeavey@users.noreply.github.com> Date: Mon, 13 Jun 2022 00:28:36 -0400 Subject: [PATCH] Add docs to storybook (#35) --- .storybook/manager.js | 4 +- .storybook/preview-head.html | 4 +- .storybook/preview.js | 15 +- .storybook/theme.js | 16 ++ __stories__/01-basic.stories.js | 5 - __stories__/assets/docs.css | 77 +++++++ __stories__/assets/prism.css | 203 ++++++++++++++++++ .../assets/{styles.css => stories.css} | 30 ++- __stories__/assets/variables.css | 15 ++ __stories__/components/Example.svelte | 8 +- __stories__/docs/01-intro.stories.mdx | 25 +++ .../docs/02-getting-started.stories.mdx | 173 +++++++++++++++ __stories__/docs/03-api-reference.stories.mdx | 68 ++++++ package.json | 4 +- 14 files changed, 617 insertions(+), 30 deletions(-) create mode 100644 .storybook/theme.js create mode 100644 __stories__/assets/docs.css create mode 100644 __stories__/assets/prism.css rename __stories__/assets/{styles.css => stories.css} (75%) create mode 100644 __stories__/assets/variables.css create mode 100644 __stories__/docs/01-intro.stories.mdx create mode 100644 __stories__/docs/02-getting-started.stories.mdx create mode 100644 __stories__/docs/03-api-reference.stories.mdx diff --git a/.storybook/manager.js b/.storybook/manager.js index b6a521c..b797428 100644 --- a/.storybook/manager.js +++ b/.storybook/manager.js @@ -1,6 +1,8 @@ // .storybook/manager.js import { addons } from '@storybook/addons' +import { themes } from '@storybook/theming' +import theme from './theme' addons.setConfig({ isFullscreen: false, @@ -10,7 +12,7 @@ addons.setConfig({ sidebarAnimations: true, enableShortcuts: true, isToolshown: true, - theme: undefined, + theme: theme, selectedPanel: undefined, initialActive: 'sidebar', sidebar: { diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index 8eaf97c..b2d7173 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -1 +1,3 @@ - + + + diff --git a/.storybook/preview.js b/.storybook/preview.js index 5eba9da..e91e9df 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1 +1,14 @@ -export const parameters = {} +import { themes } from '@storybook/theming' +import theme from './theme' + +// or global addParameters +export const parameters = { + docs: { + theme: theme, + }, + previewTabs: { + 'storybook/docs/panel': { + hidden: true, + }, + }, +} diff --git a/.storybook/theme.js b/.storybook/theme.js new file mode 100644 index 0000000..e520853 --- /dev/null +++ b/.storybook/theme.js @@ -0,0 +1,16 @@ +import { create } from '@storybook/theming' + +export default create({ + base: 'light', + colorPrimary: '#24292f', + colorSecondary: '#0969da', + fontCode: + 'ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace', + textColor: '#24292f', + barTextColor: '#24292f', + barSelectedColor: '#24292f', + // brandTitle: 'SplitType', + // brandImage: '', + // brandUrl: '', + // brandTarget: '_self', +}) diff --git a/__stories__/01-basic.stories.js b/__stories__/01-basic.stories.js index e1b86a2..5ce09e4 100644 --- a/__stories__/01-basic.stories.js +++ b/__stories__/01-basic.stories.js @@ -8,11 +8,6 @@ const { words, chars } = count(children) export default { title: 'Tests/Basic', argTypes: { ...baseArgTypes }, - parameters: { - docs: { - page: null, - }, - }, } const Template = getTemplate({ children }) diff --git a/__stories__/assets/docs.css b/__stories__/assets/docs.css new file mode 100644 index 0000000..3b02a7a --- /dev/null +++ b/__stories__/assets/docs.css @@ -0,0 +1,77 @@ +@import 'variables.css'; + +body { + font-size: var(--font-body) !important; +} + +#docs-root .sbdocs { + font-size: var(--font-size); +} +.sbdocs-content > *:first-child { + margin-top: 0 !important; +} + +#docs-root .sbdocs-h1, +#docs-root .sbdocs-h2, +#docs-root .sbdocs-h3, +#docs-root .sbdocs-h4, +#docs-root .sbdocs-h5, +#docs-root .sbdocs-h5 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +#docs-root .sbdocs-h1 { + padding-bottom: 0.3em; + font-size: 2em; + border-bottom: 1px solid var(--color-border-muted); +} + +#docs-root .sbdocs-h2 { + padding-bottom: 0.3em; + font-size: 1.5em; + border-bottom: 1px solid var(--color-border-muted); +} + +#docs-root .sbdocs-h3 { + font-size: 1.25em; +} + +#docs-root .sbdocs-h4 { + font-size: 1em; +} + +/* Top level wrapper for documentation pages */ +#docs-root .sbdocs.sbdocs-wrapper { + padding: 2rem 20px; +} + +/* Inline code styles */ +#docs-root code { + font-family: var(--font-mono); + color: inherit; + padding: 0.2em 0.4em; + margin: 0; + font-size: var(--code-font-size); + background-color: var(--color-canvas-subtle); + border-radius: 6px; + -webkit-font-smoothing: auto !important; +} + +/* Code block container */ +#docs-root .docblock-source { + margin-top: 0; + margin-bottom: 16px; + box-shadow: none; + background-color: var(--color-canvas-subtle); + border: none; + font-size: var(--font-size); +} + +/* Code block */ +#docs-root pre.prismjs { + font-size: var(--code-font-size); + -webkit-font-smoothing: auto !important; +} diff --git a/__stories__/assets/prism.css b/__stories__/assets/prism.css new file mode 100644 index 0000000..9e01866 --- /dev/null +++ b/__stories__/assets/prism.css @@ -0,0 +1,203 @@ +/* PrismJS 1.28.0 +https://prismjs.com/download.html#themes=prism-coy&languages=markup+css+clike+javascript&plugins=highlight-keywords */ +code[class*='language-'], +pre[class*='language-'] { + color: #000; + background: 0 0; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} +pre[class*='language-'] { + position: relative; + margin: 0.5em 0; + overflow: visible; + padding: 1px; +} +pre[class*='language-'] > code { + position: relative; + z-index: 1; + border-left: 10px solid #358ccb; + box-shadow: -1px 0 0 0 #358ccb, 0 0 0 1px #dfdfdf; + background-color: #fdfdfd; + background-image: linear-gradient( + transparent 50%, + rgba(69, 142, 209, 0.04) 50% + ); + background-size: 3em 3em; + background-origin: content-box; + background-attachment: local; +} +code[class*='language-'] { + max-height: inherit; + height: inherit; + padding: 0 1em; + display: block; + overflow: auto; +} +:not(pre) > code[class*='language-'], +pre[class*='language-'] { + background-color: #fdfdfd; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-bottom: 1em; +} +:not(pre) > code[class*='language-'] { + position: relative; + padding: 0.2em; + border-radius: 0.3em; + color: #c92c2c; + border: 1px solid rgba(0, 0, 0, 0.1); + display: inline; + white-space: normal; +} +pre[class*='language-']:after, +pre[class*='language-']:before { + content: ''; + display: block; + position: absolute; + bottom: 0.75em; + left: 0.18em; + width: 40%; + height: 20%; + max-height: 13em; + box-shadow: 0 13px 8px #979797; + -webkit-transform: rotate(-2deg); + -moz-transform: rotate(-2deg); + -ms-transform: rotate(-2deg); + -o-transform: rotate(-2deg); + transform: rotate(-2deg); +} +pre[class*='language-']:after { + right: 0.75em; + left: auto; + -webkit-transform: rotate(2deg); + -moz-transform: rotate(2deg); + -ms-transform: rotate(2deg); + -o-transform: rotate(2deg); + transform: rotate(2deg); +} +#docs-root .token.block-comment, +#docs-root .token.cdata, +#docs-root .token.comment, +#docs-root .token.doctype, +#docs-root .token.prolog { + font-style: normal; + color: #7d8b99; +} +#docs-root .token.punctuation { + color: #5f6364; +} +#docs-root .token.boolean, +#docs-root .token.constant, +#docs-root .token.deleted, +#docs-root .token.function-name, +#docs-root .token.number, +#docs-root .token.symbol { + color: #c92c2c; +} + +#docs-root .token.builtin, +#docs-root .token.char, +#docs-root .token.inserted, +#docs-root .token.selector, +#docs-root .token.string, +#docs-root .token.tag.attr-value, +#docs-root .token.regex, +#docs-root .token.important { + color: #0a3069; +} + +#docs-root .token.entity, +#docs-root .token.url { + color: #a67f59; + background: rgba(255, 255, 255, 0.5); +} + +#docs-root .token.variable, +#docs-root .token.class-name { + color: #953800; +} + +#docs-root .token.tag { + color: #116329; +} +#docs-root .token.atrule, +#docs-root .token.attr-value, +#docs-root .token.keyword, +#docs-root .token.operator { + color: #cf222e; +} +#docs-root .token.module #docs-root .token.method, +#docs-root .token.property-access, +#docs-root .token.function { + color: #8250df; +} + +#docs-root .token.attr-name, +#docs-root .token.number, +#docs-root .token.property { + color: #0550ae; +} + +#docs-root .language-css .token.string, +#docs-root .style .token.string { + color: #a67f59; + background: rgba(255, 255, 255, 0.5); +} +#docs-root .token.important { + font-weight: 400; +} +#docs-root .token.bold { + font-weight: 700; +} +#docs-root .token.italic { + font-style: italic; +} +#docs-root .token.entity { + cursor: help; +} +#docs-root .token.namespace { + opacity: 0.7; +} +@media screen and (max-width: 767px) { + pre[class*='language-']:after, + pre[class*='language-']:before { + bottom: 14px; + box-shadow: none; + } +} +pre[class*='language-'].line-numbers.line-numbers { + padding-left: 0; +} +pre[class*='language-'].line-numbers.line-numbers code { + padding-left: 3.8em; +} +pre[class*='language-'].line-numbers.line-numbers .line-numbers-rows { + left: 0; +} +pre[class*='language-'][data-line] { + padding-top: 0; + padding-bottom: 0; + padding-left: 0; +} +pre[data-line] code { + position: relative; + padding-left: 4em; +} +pre .line-highlight { + margin-top: 0; +} diff --git a/__stories__/assets/styles.css b/__stories__/assets/stories.css similarity index 75% rename from __stories__/assets/styles.css rename to __stories__/assets/stories.css index cb1e5e2..e925e43 100644 --- a/__stories__/assets/styles.css +++ b/__stories__/assets/stories.css @@ -1,11 +1,6 @@ +@import 'variables.css'; :root { - --text: #333; - --code: #787282; - --accent: #fb7a7a; - --primary: #1ea7fd; - --primaryDark: #077cc5; - --primaryLight: #3bb4ff; - --fontSize: 32px; + --example-font-size: 32px; } body { @@ -13,6 +8,9 @@ body { padding: 0 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + font-size: var(--font-size); + line-height: 1.5; + color: var(--color-default-fg); } .container { @@ -38,12 +36,12 @@ body { } .target { - color: var(--text); + color: var(--color-default-fg); line-height: 1.5; letter-spacing: normal; font-kerning: none; font-variant-ligatures: none; - font-size: var(--fontSize); + font-size: var(--example-font-size); } /* Make sure nested elements have same font size */ @@ -67,13 +65,13 @@ Buttons and Links } .target a { - color: var(--primary); + color: var(--color-accent-fg); } .target a:hover, .target a:active { transition: all 100ms ease-in-out; - color: var(--primaryDark); + color: var(--color-accent-dark-fg); } .target button { @@ -84,8 +82,8 @@ Buttons and Links appearance: none; cursor: pointer; outline: none; - color: var(--primary); - border: solid 1px var(--primary); + color: var(--color-accent-fg); + border: solid 1px var(--color-accent-fg); padding: 4px 10px; border-radius: 6px; font-size: inherit; @@ -94,16 +92,16 @@ Buttons and Links } .target button:hover { - background: var(--primary); + background: var(--color-accent-fg); transition: all 100ms ease-in-out; color: #fff; } .target strong { - color: var(--accent); + color: var(--color-secondary-fg); } .target code { font-family: Courier; - color: var(--code); + color: var(--color-code-fg); } diff --git a/__stories__/assets/variables.css b/__stories__/assets/variables.css new file mode 100644 index 0000000..a8d25cf --- /dev/null +++ b/__stories__/assets/variables.css @@ -0,0 +1,15 @@ +:root { + --font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, + Liberation Mono, monospace; + --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, + sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + --font-size: 16px; + --code-font-size: 85%; + --color-canvas-subtle: #f6f8fa; + --color-border-muted: #d8dee4; + --color-default-fg: #24292f; + --color-code-fg: #24292f; + --color-accent-fg: #0969da; + --color-accent-dark-fg: #0661c8; + --color-secondary-fg: #fb7a7a; +} diff --git a/__stories__/components/Example.svelte b/__stories__/components/Example.svelte index 2917200..66a7338 100644 --- a/__stories__/components/Example.svelte +++ b/__stories__/components/Example.svelte @@ -14,8 +14,6 @@ // Coerce children to an array const childrenArray = Array.isArray(children) ? children : [children] - // Reference the target element(s) - let targetElement // Reference to the container element let containerElement // Resizer observer @@ -48,7 +46,7 @@ // If supported, create a resize observer for the container element if (window.ResizeObserver !== undefined) { - resizeObserver = new ResizeObserver(debounce(handleResize, 350)) + resizeObserver = new ResizeObserver(debounce(handleResize, 100)) } // Support "types=none" as an alias for types="" @@ -56,7 +54,7 @@ onMount(() => { // Split the the target element(s) using the provided options - instance = SplitType.create(targetElement, options) + instance = SplitType.create('.target', options) resizeObserver.observe(containerElement) console.log(instance) }) @@ -68,6 +66,6 @@
{#each childrenArray as childContent} -
{@html childContent}
+
{@html childContent}
{/each}
diff --git a/__stories__/docs/01-intro.stories.mdx b/__stories__/docs/01-intro.stories.mdx new file mode 100644 index 0000000..7f1a500 --- /dev/null +++ b/__stories__/docs/01-intro.stories.mdx @@ -0,0 +1,25 @@ +import { Meta } from '@storybook/addon-docs' + + + +# SplitType + +[![npm version](https://badge.fury.io/js/split-type.svg)](https://www.npmjs.com/package/split-type) + +SplitType is a small javascript library that splits HTML text into elements so that lines, words, and characters can be animated independently. It was inspired by GSAP's SplitText plugin, and can be used with any animation library. + +Under the hood, SplitType changes the html structure of the target elements, wrapping each line, word, and/or character in a element. + +## Features + +- Splits text into lines, words, and/or characters +- Customizable class names for split text elements +- Detects natural lines breaks in the text +- Preserves explicit lines breaks (`
` tags) +- Preserves nested HTML elements inside the target elements +- Supports unicode symbols such as emojis + +## Docs + +- [Getting Started](?path=/story/getting-started--page) +- [API Reference](?path=/story/api-reference--page) diff --git a/__stories__/docs/02-getting-started.stories.mdx b/__stories__/docs/02-getting-started.stories.mdx new file mode 100644 index 0000000..897eda8 --- /dev/null +++ b/__stories__/docs/02-getting-started.stories.mdx @@ -0,0 +1,173 @@ +import { Meta } from '@storybook/addon-docs' + + + +# Getting Started + +## Installation + +```SHELL +npm install 'split-type' +``` + +```js +import SplitType from 'split-type' +``` + +Or, include the following ` +``` + +## Usage + +The SplitType class "splits" the text content of the target elements using the provided options. It returns a `SplitType` instance which provides access to the split text nodes. + +```js +const text = new SplitType('#target') +``` + +You can also use the static method `SplitType.create`, which allows you to create a splitType instance without using the `new` keyword. + +```js +// Creates a new SplitType instance +const text = SplitType.create('.target') +``` + +### Types + +The `types` option lets you specify which units the text will be broken up into. There are three types: lines, words, and characters. You can specify any combination of these types. + +For multi-line text, you need to include words and/or lines in the list of types. Splitting text into characters only will result in unexpecpted line breaks. + +```js +// Splits text into lines, words, characters (default) +const text = new SplitType('#target') +// Splits text into words and characters +const text = new SplitType('#target', { types: 'words, chars' }) +// Splits text into lines +const text = new SplitType('#target', { types: 'words' } +``` + +### Accessing split text nodes + +You can access the split lines/words/characters via properties on the `SplitType` instance. + +```js +// Splits text in element with ID="target" +const text = new SplitType('#target') + +// An array of the all line elements +console.log(text.lines) +// An array of all word elements +console.log(text.words) +// An array of all character elements +console.log(text.chars) +``` + +Or using selectors + +```js +const text = new SplitType('#target') +const words = document.querySelectorAll('#target .word') +``` + +### Nested Elements + +As of `v0.3`, SplitType will preserve nested elements when the text is split. This makes it possible to: + +- Apply custom styles to specific parts of the test +- Include interactive elements such links are buttons inside split text. + +```html +

Foo Bar

+``` + +```js +SplitType.create('#target') +``` + +Result + +```html +
+
+
+
F
+
o
+
o
+
+
+
B
+
a
+
r
+
+
+
+
+``` + +Caveat: this feature is not fully compatible with splitting text into lines. When split lines is enabled, if the text content of a nested element gets broken onto multiple lines, it will result in unexpected line breaks in the split text. + +You can use nested element when spliting text into lines, provided you can ensure that there will be no line breaks in the nested elements. + +### Absolute vs Relative position + +By default, split text nodes are set to relative position and `display:inline-block`. SplitType also supports absolute position for split text nodes by setting `{ absolute: true }`. When this is enabled, each line/word/character will be set to absolute position, which can improve performance for some animations. + +### Responsive Text + +When text is split into words and characters using relative position, the text will automatically reflow when the container is resized. However, when absolute position is enabled, or text is split into lines, the text will need to be re-split after the container is resized. + +This can be achieved by using an event listener or `ResizeObserver` to call the `split` method again after the window or container element has been resized. + +```js +// This would be a reference to the container element that contains split text +const containerElement +// the SplitType instance +const instance = new SplitType('#target') +// stores the previous width of the container element +let previousContainerWidth = null + +// An event handler that will be called when the container element is resized. +function handleResize(entry) { + let width + // Only proceed if absolute position is enabled, or text is split into lines. + if (options.absolute || /lines/.test(String(options.types))) { + // The new width of the container element + const [{ contentRect }] = entry + width = Math.floor(contentRect.width) + // only proceed if: + // 1. previousContainerWidth has been set. This avoids calling the split + // method when the resizeObserver is triggered on the initial render + // 2. the width of the container element has changed. + if (previousContainerWidth && previousContainerWidth !== width) { + // Call the split method to re-split the text. This will will reposition + // the text based on the new container size. + instance.split() + } + } + previousContainerWidth = width +} + +// This example uses lodash#debounce so the split method only gets called once +// after the resizing is complete. +const resizeObserver = new ResizeObserver(debounce(handleResize, 500)) +resizeObserver.observe(containerElement) +``` + +```js +// Split text into words and characters +const text = new SplitType('#target', { types: 'words, chars' }) + +// Animate characters into view with a stagger effect +gsap.from(text.chars, { + opacity: 0, + y: 20, + duration: 0.5, + stagger: { amount: 0.1 }, +}) +``` diff --git a/__stories__/docs/03-api-reference.stories.mdx b/__stories__/docs/03-api-reference.stories.mdx new file mode 100644 index 0000000..c7b3bb3 --- /dev/null +++ b/__stories__/docs/03-api-reference.stories.mdx @@ -0,0 +1,68 @@ +import { Meta } from '@storybook/addon-docs' + + + +# API Reference + +```js +new SplitType(target, [options]) +``` + +The splitType class splits the text content of one or more elements and returns a new `SplitType` instance. + +#### target:   `string | Element | NodeList | Element[]` + +Specifies the target elements for the SplitType call. This can be a selector string, a single `Element`, a collection of elements (such as a `NodeList`, jQuery Object, or `Array`). + +#### options:   `SplitTypeOptions` + +| name | type | default | description | +| ---------- | --------- | ----------------------- | ---------------------------------------------------------------- | +| absolute | `boolean` | `false` | If true, absolute position will be used to for split text nodes. | +| tagName | `string` | `"div"` | The HTML tag that will be used for split text nodes | +| lineClass | `string` | `"line"` | The className all split line elements | +| wordClass | `string` | `"word"` | The className for split word elements | +| charClass | `string` | `"char"` | The className for split character elements | +| splitClass | `string` | `null` | A className for all split text elements | +| types | `string` | `"lines, words, chars"` | Comma separated list of types | +| split | `string` | `""` | Alias for `types` | + +## Instance Properties + +#### `instance.lines` :  `HTMLElement[]` + +An array of all split line elements in the splitType instance. If there are multiple target elements, this will include split lines from all elements. + +#### `instance.words` : `HTMLElement[]` + +An array of the split word elements in the splitType instance. If there are multiple target elements, this will include split words from all elements. + +#### `instance.chars` : `HTMLElement[]` + +An array of the split character elements. If there are multiple target elements, this will include split chars from all elements. + +#### `instance.split(options) => void` + +This method is automatically called by the SplitType constructor. But it can be called manually to re-split text using different options. + +#### `instance.revert() => void` + +Restores the target elements associated with this SplitType instance to their original text content. It also clears cached data associated with the split text nodes. + +## Static Properties + +#### `SplitType.defaults` : `SplitTypeOptions` + +Gets the current default settings for all SplitType instances. The default settings can be modified using the `setDefaults` methods. + +#### `SplitType.setDefaults(options: any) => SplitTypeOptions` + +Sets the default options for all `SplitType` instances. The provided object will be merged with the existing `SplitType.defaults` object. Returns the new defaults object. + +#### `SplitType.create(target, options) => SplitType` + +Creates a new `SplitType` instance using the provided parameters. This method can be used to call SplitType without using the `new` keyword. Returns the SplitType instance + +#### `SplitType.revert(target)` + +Reverts the target element(s) if they are currently split. This provides a way to revert split text without a reference to the `SplitType` instance. diff --git a/package.json b/package.json index 1bc742e..2a996a9 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "husky": "^7.0.0", "jest": "27.x", "jest-transform-svelte": "^2.1.1", + "jest-transformer-mdx": "^3.3.0", "lodash": "^4.17.21", "onchange": "^7.1.0", "prettier": "^2.6.2", @@ -79,7 +80,8 @@ "jest": { "transform": { "^.+\\.js$": "babel-jest", - "^.+\\.svelte$": "jest-transform-svelte" + "^.+\\.svelte$": "jest-transform-svelte", + "^.+\\.mdx?$": "@storybook/addon-docs/jest-transform-mdx" }, "transformIgnorePatterns": [ "node_modules/(?!(@storybook/svelte)/)"