1
0
mirror of synced 2025-12-19 18:10:59 -05:00

Migrate Callout React component to Alert (#47226)

Co-authored-by: Peter Bengtsson <peterbe@github.com>
This commit is contained in:
Kevin Heis
2024-01-02 13:38:14 -08:00
committed by GitHub
parent 1868ac7b4a
commit ec4d85c7d3
18 changed files with 88 additions and 82 deletions

View File

@@ -245,3 +245,9 @@ scroll_button:
popovers: popovers:
role_description: hover card role_description: hover card
keyboard_shortcut_description: Press alt+up to activate keyboard_shortcut_description: Press alt+up to activate
alerts:
NOTE: Note
IMPORTANT: Important
WARNING: Warning
TIP: Tip
CAUTION: Caution

View File

@@ -7,7 +7,7 @@ import { ArticleGridLayout } from 'src/frame/components/article/ArticleGridLayou
import { MiniTocs } from 'src/frame/components/ui/MiniTocs' import { MiniTocs } from 'src/frame/components/ui/MiniTocs'
import { useAutomatedPageContext } from 'src/automated-pipelines/components/AutomatedPageContext' import { useAutomatedPageContext } from 'src/automated-pipelines/components/AutomatedPageContext'
import { ClientSideHighlight } from 'src/frame/components/ClientSideHighlight' import { ClientSideHighlight } from 'src/frame/components/ClientSideHighlight'
import { Callout } from 'src/frame/components/ui/Callout' import { Alert } from 'src/frame/components/ui/Alert'
type Props = { type Props = {
children: React.ReactNode children: React.ReactNode
@@ -34,9 +34,7 @@ export const AutomatedPage = ({ children }: Props) => {
{permissions && <PermissionsStatement permissions={permissions} />} {permissions && <PermissionsStatement permissions={permissions} />}
{product && ( {product && <Alert className="mb-4" html={product} />}
<Callout className="mb-4" dangerouslySetInnerHTML={{ __html: product }} />
)}
</> </>
} }
toc={miniTocItems.length > 1 && <MiniTocs miniTocItems={miniTocItems} />} toc={miniTocItems.length > 1 && <MiniTocs miniTocItems={miniTocItems} />}

View File

@@ -6,7 +6,7 @@ export const tags = {
} }
const template = const template =
'<div class="ghd-spotlight ghd-spotlight-{{ color }} my-4 pl-3 py-2">{{ output }}</div>' '<div class="ghd-alert ghd-alert-{{ color }} ghd-spotlight-{{ color }}">{{ output }}</div>'
export const Spotlight = { export const Spotlight = {
type: 'block', type: 'block',

View File

@@ -11,10 +11,15 @@ $colors: "default", "muted", "subtle", "accent", "success", "attention",
border-left: 0.25em solid border-left: 0.25em solid
var(--borderColor-default, var(--color-border-default)); var(--borderColor-default, var(--color-border-default));
margin-bottom: 1rem; margin-bottom: 1rem;
}
.ghd-alert > :last-child { > :last-child {
margin-bottom: 0; margin-bottom: 0;
}
pre {
background: transparent;
padding: 0.5rem;
}
} }
.ghd-alert-title { .ghd-alert-title {
@@ -32,4 +37,15 @@ $colors: "default", "muted", "subtle", "accent", "success", "attention",
color: var(--fgColor-#{$color}, var(--color-#{$color}-fg)); color: var(--fgColor-#{$color}, var(--color-#{$color}-fg));
} }
} }
// Temporary: so that the existing "notes" "callouts" etc color the first word
.ghd-spotlight-#{$color} {
p:first-child {
strong:first-child,
b:first-child {
color: var(--fgColor-#{$color}, var(--color-#{$color}-fg));
}
}
}
// End temporary
} }

View File

@@ -2,6 +2,5 @@
@import "heading-links.scss"; @import "heading-links.scss";
@import "images.scss"; @import "images.scss";
@import "markdown-overrides.scss"; @import "markdown-overrides.scss";
@import "spotlight.scss";
@import "syntax-highlighting.scss"; @import "syntax-highlighting.scss";
@import "alerts.scss"; @import "alerts.scss";

View File

@@ -1,26 +0,0 @@
.ghd-spotlight {
border-left: 0.25rem solid;
:last-child {
margin-bottom: 0;
}
pre {
background: transparent;
padding: 0.5rem;
}
}
$colors: "default", "muted", "subtle", "accent", "success", "attention",
"severe", "danger", "open", "closed", "done", "sponsors";
@each $color in $colors {
.ghd-spotlight-#{$color} {
border-left-color: var(--fgColor-#{$color}, var(--color-#{$color}-fg));
p:first-child strong:first-child,
b:first-child {
color: var(--fgColor-#{$color}, var(--color-#{$color}-fg));
}
}
}

View File

@@ -245,3 +245,9 @@ scroll_button:
popovers: popovers:
role_description: hover card role_description: hover card
keyboard_shortcut_description: Press alt+up to activate keyboard_shortcut_description: Press alt+up to activate
alerts:
NOTE: Note
IMPORTANT: Important
WARNING: Warning
TIP: Tip
CAUTION: Caution

View File

@@ -1,9 +1,9 @@
import { getDOM } from '#src/tests/helpers/e2etest.js' import { getDOM } from '#src/tests/helpers/e2etest.js'
describe('callouts', () => { describe('alerts', () => {
test('article page', async () => { test('article page', async () => {
const $ = await getDOM('/get-started/foo/page-with-callout') const $ = await getDOM('/get-started/foo/page-with-callout')
const callout = $('[data-testid=callout]') const callout = $('[data-testid=alert] div')
expect(callout.html()).toBe('<p>Callout for HubGit Pages</p>') expect(callout.html()).toBe('<p>Callout for HubGit Pages</p>')
}) })
@@ -11,16 +11,16 @@ describe('callouts', () => {
// This page has `product:` property which is a piece of Liquid // This page has `product:` property which is a piece of Liquid
// which makes it so that the rendered output of that becomes // which makes it so that the rendered output of that becomes
// an empty string. // an empty string.
// This test tests that callout is not rendered if its output // This test tests that alert is not rendered if its output
// "exits" but is empty. // "exits" but is empty.
const $ = await getDOM('/enterprise-server@latest/get-started/foo/page-with-callout') const $ = await getDOM('/enterprise-server@latest/get-started/foo/page-with-callout')
const callout = $('[data-testid=callout]') const callout = $('[data-testid=alert]')
expect(callout.length).toBe(0) expect(callout.length).toBe(0)
}) })
test('toc landing page', async () => { test('toc landing page', async () => {
const $ = await getDOM('/actions/category') const $ = await getDOM('/actions/category')
const callout = $('[data-testid=callout]') const callout = $('[data-testid=alert] div')
expect(callout.html()).toBe('<p>This is the callout text</p>') expect(callout.html()).toBe('<p>This is the callout text</p>')
}) })
}) })

View File

@@ -3,7 +3,7 @@ import dynamic from 'next/dynamic'
import cx from 'classnames' import cx from 'classnames'
import { LinkExternalIcon } from '@primer/octicons-react' import { LinkExternalIcon } from '@primer/octicons-react'
import { Callout } from 'src/frame/components/ui/Callout' import { Alert } from 'src/frame/components/ui/Alert'
import { DefaultLayout } from 'src/frame/components/DefaultLayout' import { DefaultLayout } from 'src/frame/components/DefaultLayout'
import { ArticleTitle } from 'src/frame/components/article/ArticleTitle' import { ArticleTitle } from 'src/frame/components/article/ArticleTitle'
import { useArticleContext } from 'src/frame/components/context/ArticleContext' import { useArticleContext } from 'src/frame/components/context/ArticleContext'
@@ -63,7 +63,7 @@ export const ArticlePage = () => {
{includesPlatformSpecificContent && <PlatformPicker />} {includesPlatformSpecificContent && <PlatformPicker />}
{includesToolSpecificContent && <ToolPicker />} {includesToolSpecificContent && <ToolPicker />}
{product && <Callout className="mb-4" dangerouslySetInnerHTML={{ __html: product }} />} {product && <Alert className="mb-4" html={product} />}
</> </>
) )

View File

@@ -134,6 +134,7 @@ export type MainContextT = {
// they will always be available and don't need to be manually added. // they will always be available and don't need to be manually added.
// Order does not matter on these. // Order does not matter on these.
const DEFAULT_UI_NAMESPACES = [ const DEFAULT_UI_NAMESPACES = [
'alerts',
'header', 'header',
'search', 'search',
'survey', 'survey',

View File

@@ -1,7 +1,4 @@
.container { .container {
p {
margin: 0;
}
a { a {
text-decoration: underline; text-decoration: underline;
} }

View File

@@ -0,0 +1,37 @@
import { createElement, ReactNode } from 'react'
import cx from 'classnames'
import styles from './Alert.module.scss'
import { InfoIcon, ReportIcon, AlertIcon, LightBulbIcon, StopIcon } from '@primer/octicons-react'
import { useTranslation } from 'src/languages/components/useTranslation'
const alertTypes = {
NOTE: { icon: InfoIcon, color: 'accent' },
IMPORTANT: { icon: ReportIcon, color: 'done' },
WARNING: { icon: AlertIcon, color: 'attention' },
TIP: { icon: LightBulbIcon, color: 'success' },
CAUTION: { icon: StopIcon, color: 'danger' },
}
export type AlertPropsT = {
html?: string
children?: ReactNode
className?: string
type?: keyof typeof alertTypes
}
export function Alert({ className, html, children, type = 'IMPORTANT' }: AlertPropsT) {
if (html && children) throw new Error("Can't specify 'html' and 'children'")
const { t } = useTranslation('alerts')
return (
<div
data-testid="alert"
className={cx(className, styles.container, `ghd-alert ghd-alert-${alertTypes[type].color}`)}
>
<p className="ghd-alert-title">
{createElement(alertTypes[type].icon, { size: 16, className: 'mr-2' })}
{t(type)}
</p>
{html ? <div dangerouslySetInnerHTML={{ __html: html }} /> : children}
</div>
)
}

View File

@@ -0,0 +1 @@
export { Alert } from './Alert'

View File

@@ -1,21 +0,0 @@
import { DOMAttributes, ReactNode } from 'react'
import cx from 'classnames'
import styles from './Callout.module.scss'
export type CalloutPropsT = {
dangerouslySetInnerHTML?: DOMAttributes<HTMLDivElement>['dangerouslySetInnerHTML']
children?: ReactNode
className?: string
}
export const Callout = ({ className, dangerouslySetInnerHTML, children }: CalloutPropsT) => {
return (
<div
data-testid="callout"
className={cx(className, styles.container, 'ghd-spotlight ghd-spotlight-done my-4 pl-3 py-2')}
dangerouslySetInnerHTML={dangerouslySetInnerHTML}
>
{children}
</div>
)
}

View File

@@ -1 +0,0 @@
export { Callout } from './Callout'

View File

@@ -1,4 +1,5 @@
import { Link } from 'src/frame/components/Link' import { Link } from 'src/frame/components/Link'
import { Alert } from 'src/frame/components/ui/Alert'
import { useTranslation } from 'src/languages/components/useTranslation' import { useTranslation } from 'src/languages/components/useTranslation'
import type { GraphqlT } from './types' import type { GraphqlT } from './types'
@@ -9,14 +10,8 @@ type Props = {
export function Notice({ item, variant = 'preview' }: Props) { export function Notice({ item, variant = 'preview' }: Props) {
const { t } = useTranslation('graphql') const { t } = useTranslation('graphql')
const previewTitle =
variant === 'preview' ? t('reference.preview_notice') : t('reference.deprecation_notice')
const noticeStyle = variant === 'preview' ? 'ghd-spotlight-accent' : 'ghd-spotlight-attention'
return ( return (
<div className={`ghd-spotlight ${noticeStyle} my-4 pl-3 py-2`}> <Alert type={variant === 'preview' ? 'NOTE' : 'WARNING'}>
<p>
<b>{previewTitle}</b>
</p>
{variant === 'preview' && item.preview ? ( {variant === 'preview' && item.preview ? (
<p> <p>
<code>{item.name}</code> is available under the{' '} <code>{item.name}</code> is available under the{' '}
@@ -37,6 +32,6 @@ export function Notice({ item, variant = 'preview' }: Props) {
/> />
</div> </div>
) : null} ) : null}
</div> </Alert>
) )
} }

View File

@@ -9,7 +9,7 @@ import { ArticleTitle } from 'src/frame/components/article/ArticleTitle'
import { MarkdownContent } from 'src/frame/components/ui/MarkdownContent' import { MarkdownContent } from 'src/frame/components/ui/MarkdownContent'
import { ArticleList } from 'src/landings/components/ArticleList' import { ArticleList } from 'src/landings/components/ArticleList'
import { ArticleGridLayout } from 'src/frame/components/article/ArticleGridLayout' import { ArticleGridLayout } from 'src/frame/components/article/ArticleGridLayout'
import { Callout } from 'src/frame/components/ui/Callout' import { Alert } from 'src/frame/components/ui/Alert'
import { Lead } from 'src/frame/components/ui/Lead' import { Lead } from 'src/frame/components/ui/Lead'
import { LearningTrackNav } from 'src/learning-track/components/article/LearningTrackNav' import { LearningTrackNav } from 'src/learning-track/components/article/LearningTrackNav'
import { ClientSideRedirects } from 'src/rest/components/ClientSideRedirects' import { ClientSideRedirects } from 'src/rest/components/ClientSideRedirects'
@@ -46,7 +46,7 @@ export const TocLanding = () => {
{intro && <Lead data-search="lead">{intro}</Lead>} {intro && <Lead data-search="lead">{intro}</Lead>}
{productCallout && <Callout dangerouslySetInnerHTML={{ __html: productCallout }} />} {productCallout && <Alert html={productCallout} />}
<div className="border-bottom border-xl-0 pb-4 mb-5 pb-xl-2 mb-xl-2" /> <div className="border-bottom border-xl-0 pb-4 mb-5 pb-xl-2 mb-xl-2" />

View File

@@ -1,3 +1,5 @@
import { Alert } from 'src/frame/components/ui/Alert'
type Props = { type Props = {
slug: string slug: string
previews: Array<string> previews: Array<string>
@@ -11,11 +13,7 @@ export function RestPreviewNotice({ slug, previews, heading }: Props) {
<a href={`#${slug}-preview-notices`}>{heading}</a> <a href={`#${slug}-preview-notices`}>{heading}</a>
</h3> </h3>
{previews.map((preview, index) => ( {previews.map((preview, index) => (
<div <Alert type="NOTE" html={preview} key={JSON.stringify(preview) + index} />
className="ghd-spotlight ghd-spotlight-accent my-4 pl-3 py-2"
dangerouslySetInnerHTML={{ __html: preview }}
key={JSON.stringify(preview) + index}
/>
))} ))}
</> </>
) )