@@ -68,24 +69,23 @@ const controls = (
)
export const Typography = () => (
-
+
{headers}
-
-
{texts}
-
-
{controls}
+
+)
+
+export const Weights = () => (
+
+
+ Text regular 400
+
+
+ Text strong 600
+
+
H4 heading 600
+
H4 strong=false 400
)
@@ -98,6 +98,129 @@ export const BaseList = () => (
)
+export const Colors = () => (
+
+ text (default): system.cpu is steady
+ textLite: last collected 3 seconds ago
+ primary: 12 nodes live
+ warning: disk space below 20%
+ error: node unreachable
+
+)
+
+export const Modifiers = () => (
+
+ truncate: a very long alert name that does not fit on a single line
+ uppercase: critical
+ capitalize: web server latency
+ textAlign center
+ textDecoration underline
+ system.cpu
+ opacity 0.5
+
+)
+
+export const Playground = args =>
+
+Playground.args = { children: "The five boxing wizards jump quickly." }
+
+const colorHint = "Theme colour key."
+
export default {
+ title: "Components/Typography",
component: Text,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component:
+ "Text and heading components (H0 to H6, TextHuge to TextFemto) plus List and ListItem. Use them instead of raw tags so size, line height, weight and colour follow the theme.",
+ },
+ },
+ },
+ argTypes: {
+ children: {
+ description: "Text content.",
+ control: "text",
+ table: { type: { summary: "ReactNode" } },
+ },
+ strong: {
+ description: "Bold (600) text. Headings are strong by default; set false for regular weight.",
+ control: "boolean",
+ table: { type: { summary: "boolean" }, defaultValue: { summary: "false (true for H0 to H6)" } },
+ },
+ color: {
+ description: colorHint,
+ control: "text",
+ table: { type: { summary: "string | string[]" }, defaultValue: { summary: "text" } },
+ },
+ truncate: {
+ description: "Single line with an ellipsis when the text overflows.",
+ control: "boolean",
+ table: { type: { summary: "boolean" } },
+ },
+ code: {
+ description: "Inline code look with a padded, rounded background.",
+ control: "boolean",
+ table: { type: { summary: "boolean" } },
+ },
+ textAlign: {
+ description: "Text alignment.",
+ options: ["left", "center", "right"],
+ control: { type: "select" },
+ table: { type: { summary: "left | center | right" } },
+ },
+ textDecoration: {
+ description: "Text decoration.",
+ options: ["none", "underline", "line-through"],
+ control: { type: "select" },
+ table: { type: { summary: "none | underline | line-through" } },
+ },
+ textTransform: {
+ description: "Text transform.",
+ options: ["none", "capitalize", "uppercase", "lowercase", "firstLetter"],
+ control: { type: "select" },
+ table: {
+ type: { summary: "none | capitalize | uppercase | lowercase | firstLetter" },
+ defaultValue: { summary: "none" },
+ },
+ },
+ whiteSpace: {
+ description: "White space handling.",
+ options: ["normal", "nowrap", "pre-line", "pre-wrap"],
+ control: { type: "select" },
+ table: { type: { summary: "normal | nowrap | pre-line | pre-wrap" } },
+ },
+ wordBreak: {
+ description: "Word break behaviour.",
+ options: ["normal", "break-all", "keep-all", "break-word"],
+ control: { type: "select" },
+ table: { type: { summary: "normal | break-all | keep-all | break-word" } },
+ },
+ opacity: {
+ description: "Opacity from 0 to 1.",
+ control: { type: "number", min: 0, max: 1, step: 0.1 },
+ table: { type: { summary: "number" } },
+ },
+ fontSize: {
+ description: "Overrides font size and line height with the same value.",
+ control: "text",
+ table: { type: { summary: "string" } },
+ },
+ lineHeight: {
+ description: "Overrides line height.",
+ control: "text",
+ table: { type: { summary: "string" } },
+ },
+ background: {
+ description: "Theme colour key for the background when code is set.",
+ control: "text",
+ table: { type: { summary: "string" }, defaultValue: { summary: "text" } },
+ },
+ as: {
+ description: "Renders another element or component.",
+ control: false,
+ table: { type: { summary: "ElementType" }, defaultValue: { summary: "span (h1 to h6 for headings)" } },
+ },
+ },
}
diff --git a/src/components/typography/typography.usage.mdx b/src/components/typography/typography.usage.mdx
new file mode 100644
index 000000000..0afc8118a
--- /dev/null
+++ b/src/components/typography/typography.usage.mdx
@@ -0,0 +1,51 @@
+import { Meta, Canvas } from "@storybook/addon-docs/blocks"
+import * as Stories from "./typography.stories"
+
+
+
+# Typography
+
+Text and heading components that apply the Netdata font, size, line height, weight and colour. Use them instead of raw tags.
+
+## Font
+
+- The font is IBM Plex Sans, set on each text component, with system sans-serif fonts as a fallback.
+
+## Headings
+
+- `H0` to `H6` are strong (600) by default. Sizes: H0 26px, H1 24px, H2 22px, H3 20px, H4 16px, H5 14px, H6 12px.
+- `H0` and `H1` render an `h1`; `H2` to `H6` render `h2` to `h6`. Use `as` when the page outline needs a different level.
+- Pass `strong={false}` for regular weight.
+
+## Text
+
+- Sizes: `TextHuge` 24px, `TextBigger` 16px, `TextBig` 14px, `Text` 12px (the regular body size), `TextSmall` 11px, `TextMicro` 10px, `TextNano` 8px, `TextFemto` 7px.
+- Text components render a `span` and are regular weight. Pass `strong` for 600.
+
+
+
+## Modifiers
+
+- `color` takes a theme colour key. Default is `main` for headings (H0 to H6) and `text` for body text. Use `textLite` for secondary text and status keys only for status.
+- `truncate` keeps one line with an ellipsis. The text needs a bounded width to truncate.
+- `textAlign`, `textDecoration`, `textTransform`, `whiteSpace`, `wordBreak`, `opacity`, margin and padding are also supported.
+- `code` gives an inline code look, e.g. a metric name like `system.cpu`.
+
+
+
+## Lists
+
+- `List` and `ListItem` render a `ul` and `li` with outside discs. They support margin, padding and `alignSelf`.
+
+
+
+## Accessibility
+
+- Choose headings for document structure, not for size. Use `as` or a different `Text` size when the level and the look differ.
+- Don't rely on colour alone to convey status.
+
+## Do and don't
+
+- Do use `Text` for body copy and `TextSmall` for captions and helper text.
+- Do keep one `H1` per page.
+- Don't set raw font sizes or colours with `style`. Use `fontSize` only when no size in the scale fits.
diff --git a/src/global-styles.js b/src/global-styles.js
index 2445e0cb3..a0dd719b9 100644
--- a/src/global-styles.js
+++ b/src/global-styles.js
@@ -1,3 +1,7 @@
+import "@fontsource/ibm-plex-sans/latin-400.css"
+import "@fontsource/ibm-plex-sans/latin-500.css"
+import "@fontsource/ibm-plex-sans/latin-600.css"
+import "@fontsource/ibm-plex-sans/latin-700.css"
import { createGlobalStyle } from "styled-components"
export const GlobalStyles = createGlobalStyle`
diff --git a/src/global-styles.test.js b/src/global-styles.test.js
new file mode 100644
index 000000000..dd60ab020
--- /dev/null
+++ b/src/global-styles.test.js
@@ -0,0 +1,11 @@
+import fs from "fs"
+import path from "path"
+
+// The font CSS is resolved by consumer bundlers (jest maps it to a proxy), so the import list is the observable contract.
+describe("GlobalStyles font loading", () => {
+ const source = fs.readFileSync(path.join(__dirname, "global-styles.js"), "utf8")
+
+ it.each([400, 500, 600, 700])("loads IBM Plex Sans latin weight %i once for every consumer", weight => {
+ expect(source).toContain(`import "@fontsource/ibm-plex-sans/latin-${weight}.css"`)
+ })
+})
diff --git a/src/hooks/useColor/index.test.js b/src/hooks/useColor/index.test.js
index d5c7efbc5..50161106a 100644
--- a/src/hooks/useColor/index.test.js
+++ b/src/hooks/useColor/index.test.js
@@ -1,10 +1,11 @@
import { renderHookWithProviders } from "testUtilities"
+import { DefaultTheme } from "@/theme"
import useColor from "./index"
describe("useColor", () => {
it("should return the desired color", () => {
const colorToReturn = "primary"
- const expetedColorHexCode = "#00AB44"
+ const expetedColorHexCode = DefaultTheme.colors.primary
const { result } = renderHookWithProviders(() => useColor(), {})
diff --git a/src/index.exports.test.js b/src/index.exports.test.js
new file mode 100644
index 000000000..8e151cc69
--- /dev/null
+++ b/src/index.exports.test.js
@@ -0,0 +1,6 @@
+import * as kit from "./index"
+
+// New public pieces from the refresh must be importable from the package root.
+it.each(["StatusWord", "MarkCounts", "useRovingFocus"])("exports %s from the package root", name => {
+ expect(typeof kit[name]).toBe("function")
+})
diff --git a/src/index.js b/src/index.js
index 7a66a7996..ec655fc5c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,4 @@
-export { Button, IconButton, ButtonGroup } from "./components/button"
+export { Button, IconButton, ButtonGroup, useRovingFocus } from "./components/button"
export { Sidebar, PortalSidebar } from "./components/sidebar"
export { Icon, iconsList, IconComponents } from "./components/icon"
@@ -65,6 +65,7 @@ export { default as MultiRangeInput } from "./components/input/multiRange"
export { default as Drop } from "./components/drops/drop"
export { default as DropContainer } from "./components/drops/container"
export { default as Tooltip } from "./components/drops/tooltip"
+export { default as TooltipInk } from "./components/drops/tooltip/tooltipInk"
export { default as Popover } from "./components/drops/popover"
export { default as Menu } from "./components/drops/menu"
export { default as MenuDropdown } from "./components/drops/menu/dropdown"
@@ -89,7 +90,6 @@ export { default as Animation } from "./components/animation"
export { default as Collapsible } from "./components/collapsible"
export { default as Documentation } from "./organisms/documentation"
-export { default as News } from "./organisms/news"
export { default as NavigationTabs } from "./organisms/navigation/tabs"
export { default as NavigationTab } from "./organisms/navigation/tab"
@@ -103,7 +103,7 @@ export { default as useNavigationArrows } from "./organisms/navigation/hooks/use
export { default as Intersection } from "./components/intersection"
export { default as useIntersection } from "./hooks/useIntersection"
-export { default as Pill } from "./components/pill"
+export { default as Pill, StatusWord, MarkCounts } from "./components/pill"
export { default as ProgressBar } from "./components/progressBar"
diff --git a/src/mixins/controlSize.js b/src/mixins/controlSize.js
new file mode 100644
index 000000000..792635ccc
--- /dev/null
+++ b/src/mixins/controlSize.js
@@ -0,0 +1,20 @@
+import { getOrElse } from "@/theme/utils"
+import { CONTROL_SIZES } from "@/theme/default/constants"
+
+const getSizes = getOrElse(["constants", "CONTROL_SIZES"], CONTROL_SIZES)
+
+// Buttons use boolean `large`/`compact`; fields use `size`. Both resolve to the same ladder.
+export const controlSizeOf = ({ size, large, compact } = {}) => {
+ if (large || size === "large") return "large"
+ if (compact || size === "compact") return "compact"
+ return "default"
+}
+
+const pick = (key, size) => props => {
+ const sizes = getSizes(props)
+ return `${(sizes[size] || sizes.default)[key]}px`
+}
+
+export const controlHeight = size => pick("height", size)
+
+export const controlFontSize = size => pick("fontSize", size)
diff --git a/src/mixins/controlSize.test.js b/src/mixins/controlSize.test.js
new file mode 100644
index 000000000..8f06f7804
--- /dev/null
+++ b/src/mixins/controlSize.test.js
@@ -0,0 +1,41 @@
+import React from "react"
+import styled, { ThemeProvider } from "styled-components"
+import { render, screen } from "@testing-library/react"
+import "jest-styled-components"
+import { DefaultTheme, DarkTheme } from "@/theme"
+import { controlHeight, controlFontSize, controlSizeOf } from "./controlSize"
+
+const Box = styled.div`
+ height: ${props => controlHeight(controlSizeOf(props))(props)};
+ font-size: ${props => controlFontSize(controlSizeOf(props))(props)};
+`
+
+const renderBox = props =>
+ render(
+
+
+
+ )
+
+describe("control size ladder", () => {
+ it("is one theme constant shared by both themes", () => {
+ expect(DefaultTheme.constants.CONTROL_SIZES).toEqual({
+ compact: { height: 24, fontSize: 12 },
+ default: { height: 28, fontSize: 13 },
+ large: { height: 32, fontSize: 14 },
+ })
+ expect(DarkTheme.constants.CONTROL_SIZES).toBe(DefaultTheme.constants.CONTROL_SIZES)
+ })
+
+ it.each([
+ [{}, "28px", "13px"],
+ [{ large: true }, "32px", "14px"],
+ [{ compact: true }, "24px", "12px"],
+ [{ size: "large" }, "32px", "14px"],
+ [{ size: "compact" }, "24px", "12px"],
+ ])("resolves %p to %s / %s", (props, height, fontSize) => {
+ renderBox(props)
+ expect(screen.getByTestId("b")).toHaveStyleRule("height", height)
+ expect(screen.getByTestId("b")).toHaveStyleRule("font-size", fontSize)
+ })
+})
diff --git a/src/mixins/focusRing.exports.test.js b/src/mixins/focusRing.exports.test.js
new file mode 100644
index 000000000..aa696a238
--- /dev/null
+++ b/src/mixins/focusRing.exports.test.js
@@ -0,0 +1,32 @@
+import React from "react"
+import styled from "styled-components"
+import { renderWithProviders, screen } from "testUtilities"
+import { DefaultTheme } from "@/theme"
+import { focusRing, focusRingOutline, fieldFocusRing, fieldFocusWithinRing } from "@/index"
+
+const Bar = styled.div`
+ ${fieldFocusWithinRing}
+`
+
+describe("focus ring mixins from the package entry", () => {
+ it("exports every focus ring", () => {
+ ;[focusRing, focusRingOutline, fieldFocusRing, fieldFocusWithinRing].forEach(mixin =>
+ expect(mixin).toBeDefined()
+ )
+ })
+
+ it("draws the field ring when anything inside the wrapper has focus", () => {
+ renderWithProviders(
+
+
+
+ )
+ const bar = screen.getByTestId("bar")
+ expect(bar).toHaveStyleRule("border-color", DefaultTheme.colors.primary, {
+ modifier: ":focus-within",
+ })
+ expect(bar).toHaveStyleRule("box-shadow", `inset 0 0 0 1px ${DefaultTheme.colors.primary}`, {
+ modifier: ":focus-within",
+ })
+ })
+})
diff --git a/src/mixins/focusRing.js b/src/mixins/focusRing.js
new file mode 100644
index 000000000..842cafb71
--- /dev/null
+++ b/src/mixins/focusRing.js
@@ -0,0 +1,36 @@
+import { css } from "styled-components"
+import { getColor, getSizeBy } from "@/theme"
+
+// Selector-agnostic ring, for controls whose visual box is a sibling of a hidden native input.
+export const focusRingOutline = css`
+ outline: ${getSizeBy(0.25)} solid ${getColor("primary")};
+ outline-offset: ${getSizeBy(0.25)};
+`
+
+export const focusRing = css`
+ &:focus {
+ outline: none;
+ }
+
+ &:focus-visible {
+ ${focusRingOutline}
+ }
+`
+
+// A 2px edge drawn inside the field (border + inset line): one clear line that overflow cannot clip.
+export const fieldFocusRing = css`
+ &:focus-visible {
+ outline: none;
+ border-color: ${getColor("primary")};
+ background-color: ${getColor("elementBackground")};
+ box-shadow: inset 0 0 0 1px ${getColor("primary")};
+ }
+`
+
+// The same edge for a wrapper whose focusable element is an input inside it.
+export const fieldFocusWithinRing = css`
+ &:focus-within {
+ border-color: ${getColor("primary")};
+ box-shadow: inset 0 0 0 1px ${getColor("primary")};
+ }
+`
diff --git a/src/mixins/focusRing.test.js b/src/mixins/focusRing.test.js
new file mode 100644
index 000000000..d62ffa7e6
--- /dev/null
+++ b/src/mixins/focusRing.test.js
@@ -0,0 +1,58 @@
+import React from "react"
+import styled, { ThemeProvider } from "styled-components"
+import { render, screen } from "@testing-library/react"
+import "jest-styled-components"
+import { DefaultTheme, DarkTheme } from "@/theme"
+import rawColors from "@/theme/rawColors"
+import { focusRing, fieldFocusRing, focusRingOutline } from "./focusRing"
+
+const Ring = styled.button`
+ ${focusRing}
+`
+const Sibling = styled.span`
+ input:focus-visible + & {
+ ${focusRingOutline}
+ }
+`
+const Field = styled.input`
+ ${fieldFocusRing}
+`
+
+const renderIn = (theme, node) => render(
{node})
+
+describe("focusRing", () => {
+ it("draws a 2px primary outline with a 2px offset on keyboard focus only", () => {
+ renderIn(DefaultTheme,
go)
+ const el = screen.getByRole("button")
+ expect(el).toHaveStyleRule("outline", "none", { modifier: ":focus" })
+ expect(el).toHaveStyleRule("outline", `2px solid ${DefaultTheme.colors.primary}`, {
+ modifier: ":focus-visible",
+ })
+ expect(el).toHaveStyleRule("outline-offset", "2px", { modifier: ":focus-visible" })
+ })
+})
+
+describe("fieldFocusRing", () => {
+ it("turns the border primary and adds a 1px inner line, no outer glow", () => {
+ renderIn(DarkTheme,
)
+ const el = screen.getByLabelText("f")
+ expect(el).toHaveStyleRule("border-color", rawColors.green.green100, {
+ modifier: ":focus-visible",
+ })
+ expect(el).toHaveStyleRule("box-shadow", `inset 0 0 0 1px ${rawColors.green.green100}`, {
+ modifier: ":focus-visible",
+ })
+ expect(el).toHaveStyleRule("outline", "none", { modifier: ":focus-visible" })
+ })
+})
+
+describe("focusRingOutline", () => {
+ it("composes the same ring onto any selector, e.g. a visual sibling of a hidden input", () => {
+ renderIn(DefaultTheme,
)
+ const el = screen.getByTestId("s")
+ expect(el).toHaveStyleRule("outline", `2px solid ${DefaultTheme.colors.primary}`, {
+ modifier: "input:focus-visible+&",
+ })
+ expect(el).toHaveStyleRule("outline-offset", "2px", { modifier: "input:focus-visible+&" })
+ })
+})
diff --git a/src/mixins/index.js b/src/mixins/index.js
index e2a80220e..b8c67cb86 100644
--- a/src/mixins/index.js
+++ b/src/mixins/index.js
@@ -10,3 +10,4 @@ export { default as round } from "./round"
export { default as textTransform } from "./textTransform"
export { default as webkitVisibleScrollbar } from "./webkitVisibleScrollbar"
export { default as zIndex } from "./zIndex"
+export { focusRing, focusRingOutline, fieldFocusRing, fieldFocusWithinRing } from "./focusRing"
diff --git a/src/mixins/segmented.js b/src/mixins/segmented.js
new file mode 100644
index 000000000..ec50d2c0f
--- /dev/null
+++ b/src/mixins/segmented.js
@@ -0,0 +1,38 @@
+import { css } from "styled-components"
+import { getColor, getRgbColor, getSizeBy } from "@/theme"
+import { focusRingOutline } from "@/mixins/focusRing"
+
+// One track for contained tabs (switch views) and segmented pickers (switch values).
+export const segmentedTrack = css`
+ display: inline-flex;
+ align-items: center;
+ gap: ${getSizeBy(0.25)};
+ padding: ${getSizeBy(0.375)};
+ border-radius: ${getSizeBy(0.5)};
+ border: 1px solid ${getColor("borderSecondary")};
+ background: ${getRgbColor("textLite", 0.12)};
+`
+
+export const segmentedItem = css`
+ border-radius: ${getSizeBy(0.25)};
+ font-weight: ${({ active }) => (active ? 600 : 500)};
+ color: ${({ active }) => getColor(active ? "textFocus" : "textLite")};
+ background: ${({ active }) => getColor(active ? "dropdown" : ["transparent", "full"])};
+ box-shadow: ${({ active, theme }) =>
+ active ? `inset 0 0 0 1px ${getColor("border")({ theme })}` : "none"};
+ transition:
+ color 120ms ease-out,
+ background 120ms ease-out;
+
+ &:hover {
+ color: ${getColor("textFocus")};
+ }
+
+ &:focus-visible {
+ ${focusRingOutline}
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ transition: none;
+ }
+`
diff --git a/src/mixins/segmented.test.js b/src/mixins/segmented.test.js
new file mode 100644
index 000000000..0f0354df2
--- /dev/null
+++ b/src/mixins/segmented.test.js
@@ -0,0 +1,41 @@
+import React from "react"
+import styled, { ThemeProvider } from "styled-components"
+import { render, screen } from "@testing-library/react"
+import "jest-styled-components"
+import { DefaultTheme } from "@/theme"
+import { segmentedTrack, segmentedItem } from "./segmented"
+
+const Track = styled.div`
+ ${segmentedTrack}
+`
+const Item = styled.button`
+ ${segmentedItem}
+`
+const { colors } = DefaultTheme
+
+describe("segmented track", () => {
+ it("draws a tinted track with a hairline border and 3px inset", () => {
+ render(
+
+
+
+ )
+ const track = screen.getByTestId("t")
+ expect(track).toHaveStyleRule("border", `1px solid ${colors.borderSecondary}`)
+ expect(track).toHaveStyleRule("padding", "3px")
+ expect(track).toHaveStyleRule("border-radius", "4px")
+ })
+
+ it("raises the active item on the dropdown surface and keeps others transparent", () => {
+ render(
+
+
+
+
+ )
+ expect(screen.getByTestId("on")).toHaveStyleRule("background", colors.dropdown)
+ expect(screen.getByTestId("on")).toHaveStyleRule("font-weight", "600")
+ expect(screen.getByTestId("off")).toHaveStyleRule("background", "rgba(255,255,255,0.0)")
+ expect(screen.getByTestId("off")).toHaveStyleRule("color", colors.textFocus, { modifier: ":hover" })
+ })
+})
diff --git a/src/mixins/visuallyHidden.js b/src/mixins/visuallyHidden.js
new file mode 100644
index 000000000..68aca6c2f
--- /dev/null
+++ b/src/mixins/visuallyHidden.js
@@ -0,0 +1,16 @@
+import { css } from "styled-components"
+
+const visuallyHidden = css`
+ border: 0;
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+`
+
+export default visuallyHidden
diff --git a/src/organisms/documentation/dashboard/section.js b/src/organisms/documentation/dashboard/section.js
index 8474d5ac7..888e94bef 100644
--- a/src/organisms/documentation/dashboard/section.js
+++ b/src/organisms/documentation/dashboard/section.js
@@ -4,7 +4,7 @@ import { Text } from "@/components/typography"
import { Icon } from "@/components/icon"
const borderProps = {
- border: { side: "top", color: "disabled" },
+ border: { side: "top", color: "border" },
padding: [6, 0, 0],
}
diff --git a/src/organisms/documentation/dashboard/section.test.js b/src/organisms/documentation/dashboard/section.test.js
new file mode 100644
index 000000000..ed4752b8f
--- /dev/null
+++ b/src/organisms/documentation/dashboard/section.test.js
@@ -0,0 +1,34 @@
+import React from "react"
+import { renderWithProviders, screen } from "testUtilities"
+import { DefaultTheme } from "@/theme/default"
+import Section from "./section"
+import Dashboard from "."
+
+describe("Dashboard help Section", () => {
+ it("divides sections with the shared border colour", () => {
+ renderWithProviders(
)
+ const root = screen.getByText("Double Click").parentElement.parentElement
+ expect(root).toHaveStyleRule("border-top", `1px solid ${DefaultTheme.colors.border}`)
+ })
+
+ it("omits the divider on the first section", () => {
+ renderWithProviders(
+
+ )
+ const root = screen.getByText("Hover").parentElement.parentElement
+ expect(root).not.toHaveStyleRule("border-top", expect.anything())
+ })
+
+ it("wraps plain string content in text", () => {
+ renderWithProviders(
)
+ expect(screen.getByText("Reset charts.")).toBeInTheDocument()
+ })
+
+ it("renders mouse instructions by default", () => {
+ renderWithProviders(
)
+ expect(screen.getByTestId("dashboard")).toBeInTheDocument()
+ expect(screen.getByText("Mouse Over / Hover")).toBeInTheDocument()
+ })
+})
diff --git a/src/organisms/documentation/general.js b/src/organisms/documentation/general.js
index 186d2edad..df5f8d094 100644
--- a/src/organisms/documentation/general.js
+++ b/src/organisms/documentation/general.js
@@ -1,6 +1,6 @@
import React, { Fragment } from "react"
import styled from "styled-components"
-import { getColor } from "@/theme"
+import { getColor, getSizeBy } from "@/theme"
import { H5, Text } from "@/components/typography"
import { Icon } from "@/components/icon"
import { Button } from "@/components/button"
@@ -12,8 +12,8 @@ const Anchor = styled(Text).attrs({ as: "a", target: "_blank" })`
color: ${getColor("primary")};
&:hover {
- color: ${getColor("accent")};
- text-decoration: none;
+ text-decoration: underline;
+ text-underline-offset: ${getSizeBy(0.25)};
}
}
`
@@ -28,6 +28,7 @@ const Section = ({
label,
labelTransform,
onClick,
+ primary = false,
}) => (
@@ -41,6 +42,7 @@ const Section = ({
onClick={onClick}
label={label}
data-testid={testid}
+ {...(!primary && { flavour: "hollow", neutral: true })}
{...(labelTransform && { textTransform: labelTransform })}
{...(url && { as: "a", target: "_blank", href: url })}
/>
@@ -103,7 +105,7 @@ const General = ({
label="Learn to use the Dashboard"
onClick={onDashboardClick}
/>
-
+
)}
{
@@ -27,8 +29,8 @@ const Header = ({ children, onClose }) => {
width="100%"
alignItems="center"
justifyContent="between"
- padding={[0, 0, 4]}
- border={{ side: "bottom", color: "disabled" }}
+ padding={[0, 0, 3]}
+ border={{ side: "bottom", color: "border" }}
>
{children}
@@ -124,7 +126,7 @@ const Documentation = ({
/>
)}
{isGeneral && (
-
+
({
+ id: { raw: id },
+ url: { raw: `https://learn.netdata.cloud/docs/${id}` },
+ title: { snippet: title },
+ description: { snippet: description },
+})
export const CloudDocumentation = () => {
return (
-
+ // Start-aligned: a full-height flex row would stretch the trigger buttons into slabs.
+
{(toggle, isOpen) => (