Skip to content

[pull] main from coreui:main #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
docs: add ExampleSnippet component
  • Loading branch information
mrholek committed Nov 30, 2024
commit 29e5aff95a61ec4e0fcdaf1a2ab28424f79b839d
3 changes: 2 additions & 1 deletion packages/coreui-react/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useClipboard } from './useClipboard'
import { useColorModes } from './useColorModes'
import { useForkedRef } from './useForkedRef'
import { usePopper } from './usePopper'

export { useColorModes, useForkedRef, usePopper }
export { useClipboard, useColorModes, useForkedRef, usePopper }
38 changes: 38 additions & 0 deletions packages/coreui-react/src/hooks/useClipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useState, useCallback } from 'react'

/**
* useClipboard Hook
*
* Provides functionality to copy text to the clipboard and track the copy status.
*
* @returns An object containing the copy function, copy status, and any error encountered.
*/
export const useClipboard = () => {
const [isCopied, setIsCopied] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)

/**
* Copies the provided text to the clipboard.
*
* @param text - The text to be copied to the clipboard.
*/
const copy = useCallback(async (text: string) => {
if (!navigator?.clipboard) {
setError(new Error('Clipboard API is not available'))
return
}

try {
await navigator.clipboard.writeText(text)
setIsCopied(true)
setError(null)
// Reset the isCopied state after 2 seconds
setTimeout(() => setIsCopied(false), 2000)
} catch (_error) {
setError(_error as Error)
setIsCopied(false)
}
}, [])

return { copy, isCopied, error }
}
1 change: 1 addition & 0 deletions packages/docs/gatsby-node.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const createPages = async ({
context: {
id: node.id,
route: node.frontmatter.route,
regex: `/^${node.frontmatter.route}/`,
},
})
})
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"author": "The CoreUI Team (https://github.com/orgs/coreui/people)",
"scripts": {
"api": "rimraf \"content/api/*\" & node build/api.mjs",
"build": "gatsby build",
"build": "gatsby build --prefix-paths",
"develop": "gatsby develop",
"dist": "run-s api build",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
Expand All @@ -33,6 +33,7 @@
"@docsearch/css": "^3.6.2",
"@mdx-js/mdx": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@stackblitz/sdk": "^1.11.0",
"@types/react-helmet": "^6.1.11",
"gatsby": "^5.13.7",
"gatsby-plugin-google-tagmanager": "^5.13.1",
Expand All @@ -58,7 +59,6 @@
"react-helmet": "^6.1.0",
"react-imask": "^7.6.1",
"react-markdown": "^9.0.1",
"remark-mdx": "^1.6.22",
"rimraf": "^6.0.1",
"sass": "^1.80.4"
},
Expand Down
172 changes: 172 additions & 0 deletions packages/docs/src/components/ExampleSnippet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import React, { FC, ReactNode, useState } from 'react'
import { Highlight, Language } from 'prism-react-renderer'

import CIcon from '@coreui/icons-react'
import { cibCodesandbox, cilCheckAlt, cilCopy } from '@coreui/icons'
import { CNav, CNavLink, CTooltip, useClipboard } from '@coreui/react'

import { openStackBlitzProject } from '../utils/stackblitz'
import { openCodeSandboxProject } from '../utils/codesandbox'

interface CodeSnippets {
js?: string
ts?: string
}

interface ExampleSnippetProps {
children: ReactNode
className?: string
code: string | CodeSnippets
codeSandbox?: boolean
componentName?: string
stackBlitz?: boolean
}

const ExampleSnippet: FC<ExampleSnippetProps> = ({
children,
className = '',
code,
codeSandbox = true,
componentName,
stackBlitz = true,
}) => {
const [language, setLanguage] = useState<'js' | 'ts'>('js')
const { copy, isCopied } = useClipboard()

// Type Guards to determine the shape of 'code' prop
const isCodeString = typeof code === 'string'
const codeJS = isCodeString ? code : code.js || code.ts
const codeTS = isCodeString ? code : code.ts
const hasJS = Boolean(codeJS)
const hasTS = Boolean(codeTS)

// Set initial language based on available code snippets
React.useEffect(() => {
if (!hasJS && hasTS) {
setLanguage('ts')
} else {
setLanguage('js')
}
}, [hasJS, hasTS])

const handleCopy = () => {
const codeToCopy = language === 'js' ? codeJS : codeTS
if (codeToCopy) {
copy(codeToCopy)
}
}

const prismLanguage: Language = language === 'js' ? 'jsx' : 'tsx'

// Determine if both languages are available
const showJSTab = hasJS && (isCodeString || code.js !== code.ts)
const showTSTab = hasTS

return (
<div className="docs-example-snippet">
{children && <div className={`docs-example ${className}`}>{children}</div>}
<div className="highlight-toolbar border-top">
<CNav className="px-3" variant="underline-border">
{showJSTab && (
<CNavLink as="button" active={language === 'js'} onClick={() => setLanguage('js')}>
JavaScript
</CNavLink>
)}
{showTSTab && (
<CNavLink as="button" active={language === 'ts'} onClick={() => setLanguage('ts')}>
TypeScript
</CNavLink>
)}
<span className="ms-auto"></span>
{codeSandbox && (
<CTooltip content="Try it on CodeSandbox">
<button
type="button"
className="btn btn-transparent"
aria-label="Try it on CodeSandbox"
onClick={() =>
openCodeSandboxProject({
name: React.isValidElement(children) && (children as any).type?.name,
language: language,
code: language === 'js' ? codeJS : codeTS || '',
componentName,
})
}
disabled={language === 'ts' && !hasTS}
>
<CIcon icon={cibCodesandbox} />
</button>
</CTooltip>
)}
{stackBlitz && (
<CTooltip content="Try it on StackBlitz">
<button
type="button"
className="btn btn-transparent px-1"
aria-label="Try it on StackBlitz"
onClick={() =>
openStackBlitzProject({
name: React.isValidElement(children) && (children as any).type?.name,
language: language,
code: language === 'js' ? codeJS : codeTS || '',
componentName,
})
}
disabled={language === 'ts' && !hasTS}
>
<svg
className="icon"
width="56"
height="78"
viewBox="0 0 56 78"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M23.4273 48.2853C23.7931 47.5845 23.0614 46.8837 22.3298 46.8837H1.11228C0.0148224 46.8837 -0.350997 45.8326 0.380642 45.1318L40.9866 0.282084C41.7182 -0.418693 43.1815 0.282084 42.8157 1.33325L32.9386 30.0651C32.5727 30.7659 32.9386 31.4666 33.6702 31.4666H54.8877C55.9852 31.4666 56.351 32.5178 55.6194 33.2186L15.0134 77.7179C14.2818 78.4187 12.8185 77.7179 13.1843 76.6667L23.4273 48.2853Z"
fill="currentColor"
/>
</svg>
</button>
</CTooltip>
)}
<CTooltip content={isCopied ? 'Copied' : 'Copy to clipboard'}>
<button
type="button"
className="btn btn-transparent px-1"
aria-label="Copy to clipboard"
onClick={handleCopy}
disabled={(language === 'js' && !hasJS) || (language === 'ts' && !hasTS)}
>
<CIcon icon={isCopied ? cilCheckAlt : cilCopy} />
</button>
</CTooltip>
</CNav>
</div>

<div className="highlight">
<Highlight
code={language === 'js' ? codeJS : codeTS || ''}
language={prismLanguage}
theme={{ plain: {}, styles: [] }}
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre className={className} style={style}>
{tokens.map((line, i) => (
<div {...getLineProps({ line, key: i })} key={i}>
{line.map((token, key) => (
<span {...getTokenProps({ token, key })} key={key} />
))}
</div>
))}
</pre>
)}
</Highlight>
</div>
</div>
)
}

ExampleSnippet.displayName = 'ExampleSnippet'

export default ExampleSnippet
2 changes: 2 additions & 0 deletions packages/docs/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Callout from './Callout'
import CodeBlock from './CodeBlock'
import ClassNamesDocs from './ClassNamesDocs'
import Example from './Example'
import ExampleSnippet from './ExampleSnippet'
import Footer from './Footer'
import Header from './Header'
import JSXDocs from './JSXDocs'
Expand All @@ -20,6 +21,7 @@ export {
CodeBlock,
ClassNamesDocs,
Example,
ExampleSnippet,
Footer,
Header,
JSXDocs,
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/pages/404.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import { CButton } from '@coreui/react/src/index'

import Seo from './../components/Seo'
import Seo from '../components/Seo'

const NotFoundPage = () => {
const { site } = useStaticQuery(query)
Expand Down
32 changes: 31 additions & 1 deletion packages/docs/src/styles/_component-examples.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,39 @@
.docs-example-snippet {
border: solid var(--cui-border-color);
border-width: 1px 0;
margin: 0 ($cd-gutter-x * -.5) 1rem ($cd-gutter-x * -.5);
padding: 0;
@include border-radius(0);

@include media-breakpoint-up(md) {
.docs-example {
margin: 0;
padding: 1rem;
border-width: 0 1px 0 0;
}

.highlight-toolbar {
border-top: 1px solid var(--cui-border-color);
}

.highlight {
margin: 0;
padding: 1rem;
}

.docs-example,
.highlight {
border: 0
}

.highlight {
margin-bottom: 0;
@include border-top-radius(0);
}

@include media-breakpoint-up(sm) {
margin: 0 0 1rem 0;
border-width: 1px;
@include border-radius(var(--cui-border-radius));
}
}

Expand Down
19 changes: 8 additions & 11 deletions packages/docs/src/templates/DocsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,13 @@ const DocsLayout: FC<DocsLayoutProps> = ({ children, data, ___location, pageContext
const frameworks = other_frameworks ? other_frameworks.split(', ') : false
const otherFrameworks = JSON.parse(JSON.stringify(jsonData))

const hasNav = data.allMdx.edges.length > 1
const hasNavAccesibility = data.allMdx.edges.filter((edge: any) =>
edge.node.frontmatter.title.includes('Accesibility'),
).length
const hasNavAPI = data.allMdx.edges.filter((edge: any) =>
edge.node.frontmatter.title.includes('API'),
).length
const hasNavCustomizing = data.allMdx.edges.filter((edge: any) =>
edge.node.frontmatter.title.includes('Customizing'),
).length
const hasNav = data?.allMdx?.edges.length > 1
const hasNavAccesibility =
hasNav && data.allMdx.edges.some((edge: any) => edge.node.fields.slug.includes('accesibility'))
const hasNavAPI =
hasNav && data.allMdx.edges.some((edge: any) => edge.node.fields.slug.includes('api'))
const hasNavCustomizing =
hasNav && data.allMdx.edges.some((edge: any) => edge.node.fields.slug.includes('customizing'))

return (
<>
Expand All @@ -52,7 +49,7 @@ const DocsLayout: FC<DocsLayoutProps> = ({ children, data, ___location, pageContext
<CNav className="ms-lg-4 docs-nav bg-body" variant="underline-border">
<CNavItem>
<CNavLink href={`${route}`} active={route === ___location.pathname}>
Overview
Features
</CNavLink>
</CNavItem>
{hasNavAPI && (
Expand Down
Loading