)
})
ClientsMarquee.displayName = 'ClientsMarquee'
================================================
FILE: docs/src/components/clients/Filters.js
================================================
import React from 'react'
export const Filters = () => (
<>
>
)
================================================
FILE: docs/src/components/forwardRefWithAs.js
================================================
import * as React from 'react';
/**
* React.Ref uses the readonly type `React.RefObject` instead of
* `React.MutableRefObject`, We pretty much always assume ref objects are
* mutable (at least when we create them), so this type is a workaround so some
* of the weird mechanics of using refs with TS.
*/
/**
* This is a hack for sure. The thing is, getting a component to intelligently
* infer props based on a component or JSX string passed into an `as` prop is
* kind of a huge pain. Getting it to work and satisfy the constraints of
* `forwardRef` seems dang near impossible. To avoid needing to do this awkward
* type song-and-dance every time we want to forward a ref into a component
* that accepts an `as` prop, we abstract all of that mess to this function for
* the time time being.
*
* TODO: Eventually we should probably just try to get the type defs above
* working across the board, but ain't nobody got time for that mess!
*
* @param Comp
*/
export function forwardRefWithAs(comp) {
return React.forwardRef(comp);
}
/*
Test components to make sure our dynamic As prop components work as intended
type PopupProps = {
lol: string;
children?: React.ReactNode | ((value?: number) => JSX.Element);
};
export const Popup = forwardRefWithAs(
({ as: Comp = 'input', lol, className, children, ...props }, ref) => {
return (
{typeof children === 'function' ? children(56) : children}
);
}
);
export const TryMe1: React.FC = () => {
return ;
};
export const TryMe2: React.FC = () => {
let ref = React.useRef(null);
return ;
};
export const TryMe4: React.FC = () => {
return ;
};
export const Whoa: React.FC<{
help?: boolean;
lol: string;
name: string;
test: string;
}> = props => {
return ;
};
*/
// export const TryMe3: React.FC = () => {
// return ;
// };
// let Cool = styled(Whoa)`
// padding: 10px;
// `
================================================
FILE: docs/src/components/markdown.module.css
================================================
/* purgecss start ignore */
.markdown {
@apply leading-relaxed text-gray-800 text-base;
}
/*
Add fake margin to offset the nav. This technique is stolen right from the Tailwind docs
@see https://tailwindcss.com/docs/customizing-colors/#app
*/
.markdown > h1:before,
.markdown > a > h1:before,
.markdown > h2:before,
.markdown > a > h2:before,
.markdown > h3:before,
.markdown > a > h3:before,
.markdown > h4:before,
.markdown > a > h4:before,
.markdown > h5:before,
.markdown > a > h5:before,
.markdown > h6:before,
.markdown > a > h6:before {
display: block;
height: 6rem;
margin-top: -6rem;
visibility: hidden;
content: '';
}
.markdown > h1,
.markdown > a > h1 {
@apply mb-6 text-4xl font-semibold leading-snug tracking-tighter text-gray-900;
}
.markdown > h2,
.markdown > a > h2 {
@apply mt-12 mb-4 text-2xl font-semibold text-gray-900;
}
.markdown > h2 code,
.markdown > a > h2 code {
@apply text-xl;
}
.markdown > h3,
.markdown > a > h3 {
@apply mt-8 mb-3 text-xl font-semibold text-gray-900;
}
.markdown > h3 code,
.markdown > a > h3 code {
@apply text-lg;
}
.markdown > h4,
.markdown > a > h4 {
@apply pt-8 mb-3 text-lg font-semibold text-gray-900;
}
.markdown > h4 code,
.markdown > a > h4 code {
@apply text-base;
}
.markdown > p > a,
.markdown > ul > li > a,
.markdown > ol > li > a,
.markdown > table > tbody > tr > td > a {
@apply text-blue-600 font-semibold transition-colors duration-150 ease-out;
}
.markdown > p > a:hover,
.markdown > ul > li > a:hover,
.markdown > ol > li > a:hover,
.markdown > table > tbody > tr > td > a:hover {
@apply text-blue-800 transition-colors duration-150 ease-out;
}
.markdown strong {
@apply font-bold;
}
.markdown > p,
.markdown > ul,
.markdown > ol,
.markdown > blockquote,
.markdown > pre,
.markdown > div > .code-block {
@apply mb-4;
}
.markdown > ul,
.markdown > ul > li > ul {
@apply ml-8 list-disc;
}
.markdown > ol {
@apply ml-8 list-decimal;
}
.markdown > pre,
.markdown > div > .code-block > pre {
color: #f8f8f2;
overflow: auto;
}
.markdown > table {
@apply mb-6 w-full text-gray-700 text-sm;
}
.markdown > table > thead > tr {
@apply border-b border-t;
}
.markdown > table > thead > tr > th {
@apply px-3 py-2 text-left text-sm font-bold bg-gray-100 text-gray-700;
}
.markdown > table > tbody > tr {
@apply border-b;
}
.markdown > table > tbody > tr > td {
@apply p-3;
}
.markdown > :not(pre):not(h3) > code,
.markdown > ul > li > code,
.markdown > blockquote > code,
.markdown > blockquote > p > code,
.markdown > ul > li > ul > li > code,
.markdown > ol > li > code,
.markdown > ol > li > ol > li > code,
.markdown > p a > code,
.markdown > table > tbody > tr > td:not(:first-child) > code {
@apply bg-gray-100 rounded-md px-1 border border-gray-300 text-gray-900;
margin-left: 2px;
margin-right: 2px;
padding-top: 2px;
padding-bottom: 2px;
word-break: keep-all;
font-size: 0.875em;
}
.markdown > table > tbody > tr > td:not(:first-child) > code {
@apply text-xs;
}
.markdown > table > tbody > tr > td > a > code,
.markdown > table > tbody > tr > td > code {
@apply text-sm;
}
.markdown > h2 > code {
@apply text-xl;
}
.markdown > .markdown ul {
@apply list-disc;
}
.markdown > hr {
@apply my-6 block border-b;
}
/* Blockquotes */
.markdown blockquote {
@apply p-3 bg-gray-100 mb-6 border border-gray-300 border-l-4 rounded-sm text-sm leading-5;
}
.markdown blockquote > p {
@apply mb-0;
}
/* purgecss end ignore */
================================================
FILE: docs/src/components/useBytesSubmit.js
================================================
import { useState } from 'react';
function sendBytesOptIn({ email, influencer, source, referral }) {
return fetch(`https://bytes.dev/api/bytes-optin-cors`, {
method: 'POST',
body: JSON.stringify({ email, influencer, source, referral }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
}).then((res) => res.json())
}
export default function useBytesSubmit() {
const [state, setState] = useState("initial");
const [error, setError] = useState(null);
const handleSubmit = (e) => {
e.preventDefault();
const email = e.target.email_address.value;
setState("loading");
sendBytesOptIn({ email, influencer: "tanstack" })
.then(() => {
setState("submitted");
})
.catch((err) => {
setError(err);
});
};
return { handleSubmit, state, error };
}
================================================
FILE: docs/src/components/useClipboard.js
================================================
import * as React from 'react';
import copy from 'copy-to-clipboard';
/**
* React hook to copy content to clipboard
*
* @param text the text or value to copy
* @param timeout delay (in ms) to switch back to initial state once copied.
*/
export function useClipboard(text, timeout = 1500) {
const [hasCopied, setHasCopied] = React.useState(false);
const onCopy = React.useCallback(() => {
const didCopy = copy(text);
setHasCopied(didCopy);
}, [text]); // @ts-ignore
React.useEffect(() => {
if (hasCopied) {
const id = setTimeout(() => {
setHasCopied(false);
}, timeout);
return () => clearTimeout(id);
}
}, [timeout, hasCopied]);
return [hasCopied, onCopy];
}
================================================
FILE: docs/src/components/useIsMobile.js
================================================
import { useState, useCallback, useEffect } from 'react'
const useMediaQuery = width => {
const [targetReached, setTargetReached] = useState(false)
const updateTarget = useCallback(e => {
if (e.matches) {
setTargetReached(true)
} else {
setTargetReached(false)
}
}, [])
useEffect(() => {
const media = window.matchMedia(`(max-width: ${width}px)`)
media.addListener(updateTarget) // Check on mount (callback is not called until a change occurs)
if (media.matches) {
setTargetReached(true)
}
return () => media.removeListener(updateTarget)
}, [])
return targetReached
}
const useIsMobile = () => {
return useMediaQuery(1024)
}
export { useMediaQuery, useIsMobile }
================================================
FILE: docs/src/components/useOverScroll.js
================================================
import * as React from 'react';
import { useViewportScroll, useTransform } from 'framer-motion';
import { throttle } from './utils/throttle';
const throttleFn = cb => throttle(cb, 100);
export const useOverScroll = () => {
const {
scrollY
} = useViewportScroll();
const ref = React.useRef(null);
const refInner = React.useRef(null);
const [height, setHeight] = React.useState(typeof window !== 'undefined' ? window.innerHeight : 0);
const [rect, setRect] = React.useState({
top: 0,
left: 0,
right: 0,
bottom: 0,
x: 0,
y: 0
});
const [rectInner, setRectInner] = React.useState({
top: 0,
left: 0,
right: 0,
bottom: 0,
x: 0,
y: 0
});
const y = useTransform(scrollY, [rect.bottom - height, rect.top + 300], [0, -rectInner.height + rect.height]);
React.useEffect(() => {
if (ref.current) {
setRect(ref.current.getBoundingClientRect());
}
}, [setRect, ref]);
React.useEffect(() => {
if (refInner.current) {
setRectInner(refInner.current.getBoundingClientRect());
}
}, [setRectInner, refInner]);
return {
ref,
refInner,
y
};
};
================================================
FILE: docs/src/components/useSearch.js
================================================
import React from 'react'
import { createPortal } from 'react-dom'
import Router from 'next/router'
import Head from 'next/head'
import Link from 'next/link'
import { useDocSearchKeyboardEvents } from '@docsearch/react'
import { siteConfig } from 'siteConfig'
const SearchContext = React.createContext()
let DocSearchModal = null
export const useSearch = () => React.useContext(SearchContext)
export function SearchProvider({
children,
searchParameters = {
hitsPerPage: 5,
},
}) {
const [isShowing, setIsShowing] = React.useState(false)
const onOpen = React.useCallback(function onOpen() {
function importDocSearchModalIfNeeded() {
if (DocSearchModal) {
return Promise.resolve()
}
return import('@docsearch/react/modal').then(
({ DocSearchModal: Modal }) => (DocSearchModal = Modal)
)
}
importDocSearchModalIfNeeded().then(() => {
setIsShowing(true)
})
}, [])
const onClose = React.useCallback(() => setIsShowing(false), [])
useDocSearchKeyboardEvents({
isOpen: isShowing,
onOpen,
onClose,
})
const options = {
appId: siteConfig.algolia.appId,
apiKey: siteConfig.algolia.apiKey,
indexName: siteConfig.algolia.indexName,
renderModal: true,
}
return (
<>
{children}
{isShowing &&
createPortal(
{
return items.map(item => {
const url = new URL(item.url)
return {
...item,
url: item.url
.replace(url.origin, '')
.replace('#__next', '')
.replace('/docs/#', '/docs/overview#'),
}
})
}}
hitComponent={Hit}
/>,
document.body
)}
>
)
}
function Hit({ hit, children }) {
return (
{children}
)
}
================================================
FILE: docs/src/components/useTocHighlight.js
================================================
import React from 'react';
/**
* Sets up Table of Contents highlighting. It requires that
*/
export function useTocHighlight(linkClassName, linkActiveClassName, topOffset, getHeaderAnchors, getHeaderDataFromAnchor, getAnchorHeaderIdentifier) {
const [lastActiveLink, setLastActiveLink] = React.useState(undefined);
const [headings, setHeadings] = React.useState([]);
React.useEffect(() => {
setHeadings(getHeaderAnchors().map(getHeaderDataFromAnchor));
}, [setHeadings]);
React.useEffect(() => {
let headersAnchors = [];
let links = [];
function setActiveLink() {
function getActiveHeaderAnchor() {
let index = 0;
let activeHeaderAnchor = null;
headersAnchors = getHeaderAnchors();
while (index < headersAnchors.length && !activeHeaderAnchor) {
const headerAnchor = headersAnchors[index];
const {
top
} = headerAnchor.getBoundingClientRect();
if (top >= 0 && top <= topOffset) {
activeHeaderAnchor = headerAnchor;
}
index += 1;
}
return activeHeaderAnchor;
}
const activeHeaderAnchor = getActiveHeaderAnchor();
if (activeHeaderAnchor) {
let index = 0;
let itemHighlighted = false;
links = document.getElementsByClassName(linkClassName);
while (index < links.length && !itemHighlighted) {
const link = links[index];
const {
href
} = link;
const anchorValue = decodeURIComponent(href.substring(href.indexOf('#') + 1));
if (getAnchorHeaderIdentifier(activeHeaderAnchor) === anchorValue) {
if (lastActiveLink) {
lastActiveLink.classList.remove(linkActiveClassName);
}
link.classList.add(linkActiveClassName);
setLastActiveLink(link);
itemHighlighted = true;
}
index += 1;
}
}
}
document.addEventListener('scroll', setActiveLink);
document.addEventListener('resize', setActiveLink);
setActiveLink();
return () => {
document.removeEventListener('scroll', setActiveLink);
document.removeEventListener('resize', setActiveLink);
};
});
return headings;
}
================================================
FILE: docs/src/components/utils/throttle.js
================================================
export const throttle = (func, limit) => {
let inThrottle;
return function () {
const args = arguments; // @ts-ignore
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
};
================================================
FILE: docs/src/manifests/getManifest.js
================================================
import manifest from './manifest.json'
const versions = {}
export const versionList = Object.keys(versions)
export const getManifest = tag => {
return tag ? versions[tag] : manifest
}
================================================
FILE: docs/src/manifests/manifest.json
================================================
{
"routes": [
{
"title": "Documentation",
"heading": true,
"routes": [
{
"title": "Overview",
"path": "/docs/overview",
"editUrl": "/docs/overview.md"
},
{
"title": "Installation",
"path": "/docs/installation",
"editUrl": "/docs/installation.md"
},
{
"title": "Getting Started",
"path": "/docs/getting-started",
"editUrl": "/docs/getting-started.md"
},
{
"title": "API Reference",
"path": "/docs/api",
"editUrl": "/docs/api.md"
}
]
},
{
"title": "Examples",
"open": true,
"routes": [
{
"title": "Simple",
"path": "/examples/simple",
"editUrl": "/examples/simple.md"
}
]
}
]
}
================================================
FILE: docs/src/pages/_app.js
================================================
import React from 'react'
import '@docsearch/react/dist/style.css'
import '../styles/index.css'
import Head from 'next/head'
import { SearchProvider } from 'components/useSearch'
function loadScript(src, attrs = {}) {
if (typeof document !== 'undefined') {
const script = document.createElement('script')
script.async = true
script.defer = true
Object.keys(attrs).forEach(attr => script.setAttribute(attr, attrs[attr]))
script.src = src
document.body.appendChild(script)
}
}
function MyApp({ Component, pageProps }) {
React.useEffect(() => {
loadScript('https://buttons.github.io/buttons.js')
}, [])
return (
<>
>
)
}
export default MyApp
================================================
FILE: docs/src/pages/_document.js
================================================
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
)
}
}
export default MyDocument
================================================
FILE: docs/src/pages/docs/api.md
================================================
---
id: api
title: API
---
> 🚨 This documentation is for **Version 3.0.0, which is currently in beta and is a work-in-progress**. For now, please refer to the examples for any missing documentation.
## Memoize your Props!
The React Charts `` component has a few options that need to be **stable** or **memoized using either `React.useMemo` or `React.useCallback`**. Using an unstable option incorrectly shouldn't severly break any basic functionality, but could results in infinite change-detection loops in your app or at the very least, your charts will be severly non-performant. If an option says it needs to be stable, it's not kidding around!
## Data Model
React Charts uses a data shape based on **arrays of series and nested arrays of datums in those series**.
```js
const data = [
{
label: 'Purchases',
data: [
{
date: new Date(),
stars: 299320,
},
// ...etc
],
},
]
```
Visualization data can come in practically any shape and size, so **memoization of data into this shape is almost always necessary**.
```tsx
const data = React.useMemo(
() => [
{
label: 'Series 1',
data: [
// ...
],
},
{
label: 'Series 2',
data: [
// ...
],
},
{
label: 'Series 3',
data: [
// ...
],
},
],
[]
)
```
The individual datums in a series' `data` array can be anything you want! Just remember that most of the types for React Charts will require you to pass the type of your `Datum`s as the first generic type to work correctly.
## Chart Component Props
The Chart component props can be passed in its `options` property object:
```javascript
```
The data property should be an array of series, each series should be an array of objects.
Each object should have two properties, by convention called primary and secondary, but it can be named as you want. One of these properties will be used as the primary axis and the other as the secondary axes.
```javascript
const data = [
{
label: 'Series 1',
data: [
{
primary: '2022-02-03T00:00:00.000Z',
likes: 130,
},
{
primary: '2022-03-03T00:00:00.000Z',
likes: 150,
},
],
},
{
label: 'Series 2',
data: [
{
primary: '2022-04-03T00:00:00.000Z',
likes: 200,
},
{
primary: '2022-05-03T00:00:00.000Z',
likes: 250,
},
],
},
]
```
The `primaryAxis` and `secondaryAxes` options, should have a prop called getValue, which is a getter function that returns the axis value for the datum. Example:
```javascript
const primaryAxis = React.useMemo(
() => ({
getValue: (datum: { primary: string }) => datum.primary,
}),
[]
)
const secondaryAxes = React.useMemo(
() => [
{
getValue: (datum: { likes: number }) => datum.likes,
elementType: 'area',
},
],
[]
)
```
The `initialHeight` and `initialWidth` expect a number, a default value is applied for each of those, 300 and 200 respectively. It's important to mention that these options are available SSR only.
If you'd like to have a custom height and width in the client side you may have a wrapper div that sets the width and height CSS attributes
`interactionMode` expect an string wich can be "primary" or "closest". It's been using for the tooltip position. By default, primary is being set.
`showVoronoi` expect a boolean, it's a debug option to visualize the interaction click-map that sits on top of the chart.
`getSeriesOrder` expect a function, this option will allows you to reorder the series if you want.
`primaryCursor` and `secondaryCursor` take the options that configure the line/rectangle that is drawn underneath your cursor when you hover over the chart. When both are used, it produces a kind of cross-hair. Both are set to true by default.
## Axes
React Charts use axes to configure a fair amount of the charts. Axes handle many things like:
- Accessing chart values from your series' `Datum`s
- Optionally positioning your axis on the grid
- Optionally configuring the scale type for your axis
- Optionally configuring the element type for series that are tied to your axis
To date, we have the following scale types available:
- `linear` - A continuous axis used for plotting numerical data on an evenly distributed scale. Works well both as a **primary and secondary** axis.
- `band` - A banded axis commonly used to plot categories or ordinal information. Works well as the **primary** axis for bar charts with ordinal domains.
- `time` - A continuous axis used for plotting UTC `Date`s on an evenly distributed scale. Works well as a **primary** axis.
- `timeLocal` - Similar to the `time` scale, but uses localized `Date` objects instead of UTC. Works well as a **primary** axis.
- `log` - A continuous axis used for plotting numerical data on a logarithmically distributed scale. Works well as a **secondary** axis
Axes are a required component of a React Chart. Both a `primaryAxis` and at least one axis vis `secondaryAxes` must be configured.
```javascript
import { Chart } from 'react-charts'
type MyDatum = { date: Date, stars: number }
function MyChart() {
const data = [
{
label: 'React Charts',
data: [
{
date: new Date(),
stars: 23467238,
},
],
},
]
const primaryAxis = React.useMemo(
(): AxisOptions => ({
getValue: datum => datum.date,
}),
[]
)
const secondaryAxes = React.useMemo(
(): AxisOptions[] => [
{
getValue: datum => datum.stars,
},
],
[]
)
return (
)
}
```
## Secondary Axis Element Types
Secondary axes for the most part are automatic, but can be optionally configured to render their respective series using 3 different element types using the `AxisOptions['scaleType']` property:
- `line`
- 1 Line per series (optional, eg. for Bubble/Scatter charts)
- 1 Circle per datum (optional)
- `area`
- 1 Enclosed area per series
- 1 Line per series (optional)
- 1 Circle per datum (optional)
- `bar`
- 1 Rectangle per datum
Example
```javascript
const secondaryAxes = React.useMemo(
(): AxisOptions[] => [
{
getValue: datum => datum.stars,
elementType: 'bar',
},
],
[]
)
```
### Chart Component Props
The Chart component props can be passed in its **options** property object:
```javascript
```
The data property should be an array of series, each series should be an array of objects.
Each object should have two properties, by convention called primary and secondary, but it can be named as you want. One of these properties will be used as the primary axis and the other as the secondary axes.
```javascript
const data = [
{
label: 'Series 1',
data: [
{
primary: '2022-02-03T00:00:00.000Z',
likes: 130,
},
{
primary: '2022-03-03T00:00:00.000Z',
likes: 150,
},
],
},
{
label: 'Series 2',
data: [
{
primary: '2022-04-03T00:00:00.000Z',
likes: 200,
},
{
primary: '2022-05-03T00:00:00.000Z',
likes: 250,
},
],
},
]
```
The **primaryAxis** and **secondaryAxes** options, should have a prop called getValue, which is a getter function that returns the axis value for the datum. Example:
```javascript
const primaryAxis = React.useMemo(
() => ({
getValue: (datum: { primary: string }) => datum.primary,
}),
[]
)
const secondaryAxes = React.useMemo(
() => [
{
getValue: (datum: { likes: number }) => datum.likes,
elementType: 'area',
},
],
[]
)
```
**initialHeight** and **initialWidth** expect a number, a default value is applied for each of those, 300 and 200 respectively. It's important to mention that these options are available SSR onoly. If you'd like to have a custom height and width in the client side you may have a wrapper div that sets the width and height CSS attributes
**interactionMode** expect an string wich can be "primary" or "closest". It's been using for the tooltip position. By default, primary is being set.
**showVoronoi** expect a boolean, it's a debug option to visualize the interaction click-map that sits on top of the chart.
**getSeriesOrder** expect a function, this option will allows you to reorder the series if you want.
**primaryCursor** and **secondaryCursor** take the options that configure the line/rectangle that is drawn underneath your cursor when you hover over the chart. When both are used, it produces a kind of cross-hair. Both are set to true by default.
### Curve Types
All element types that support lines or curves can be configured by passing any `curve` generator function as the `AxisOptions['curve']` option. By default, horizontal and vertical series default to using `monotoneX` and `monotoneY` curves, respectively. More information can be found at [`d3-shape curves`](https://github.com/d3/d3-shape#curves)
# API
> Coming Soon! Feel free to consult the **[examples](/examples/simple)** or refer to the exported types in your favorite IDE for now.
================================================
FILE: docs/src/pages/docs/getting-started.md
================================================
---
id: getting-started
title: Getting Started
---
Out of the box, React Charts is very forgiving and requires very little to use. Let's get started by going over the bare minimum configuration required to render a chart!
- An array of `Series` objects (more on this in a bit), each with a `data` property that is an array of `Datums` (be patient, we'll explain soon!)
```ts
type DailyStars = {
date: Date,
stars: number,
}
type Series = {
label: string,
data: DailyStars[]
}
const data: Series[] = [
{
label: 'React Charts',
data: [
{
date: new Date(),
stars: 202123,
}
// ...
]
},
{
label: 'React Query',
data: [
{
date: new Date(),
stars: 10234230,
}
// ...
]
}
]
```
- `primaryAxis: AxisOptions`
- Primary Value Accessor
- `secondaryAxes: AxisOptions[]`
- Primary Value Accessor
```tsx
function App() {
const primaryAxis = React.useMemo(
(): AxisOptions => ({
getValue: datum => datum.date,
}),
[]
)
const secondaryAxes = React.useMemo(
(): AxisOptions[] => [
{
getValue: datum => datum.stars,
},
],
[]
)
return (
)
}
```
Now that you know how to build a simple chart, let's dive deeper!
================================================
FILE: docs/src/pages/docs/installation.md
================================================
---
id: installation
title: Installation
---
You can install React Charts with [NPM](https://npmjs.com),
[Yarn](https://yarnpkg.com), or a good ol' `