@chronogrove/ui 0.79.0 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +40 -19
  2. package/package.json +73 -6
  3. package/src/__snapshots__/header.spec.js.snap +8 -8
  4. package/src/__snapshots__/theme.spec.js.snap +39 -20
  5. package/src/action-button.js +6 -6
  6. package/src/action-button.spec.js +14 -2
  7. package/src/action-card-layout.js +13 -0
  8. package/src/action-card-layout.spec.js +13 -0
  9. package/src/animated-page-background/ChronogroveAnimatedPageBackground.js +153 -0
  10. package/src/animated-page-background/ChronogroveAnimatedPageBackground.spec.js +189 -0
  11. package/src/animated-page-background/ColorBends.js +309 -0
  12. package/src/animated-page-background/color-bends.css +13 -0
  13. package/src/animated-page-background/index.js +2 -0
  14. package/src/animated-page-background/index.spec.js +18 -0
  15. package/src/button.js +4 -3
  16. package/src/category-label.js +23 -0
  17. package/src/category-label.spec.js +24 -0
  18. package/src/chevron-icons.js +37 -0
  19. package/src/chronogrove-theme-surface-colors.js +22 -0
  20. package/src/color-mode/browser-sync.js +7 -0
  21. package/src/color-mode/browser-sync.spec.js +7 -0
  22. package/src/color-mode/chronogrove-head-theme.js +22 -0
  23. package/src/color-mode/head-inline.js +40 -5
  24. package/src/color-mode/head-inline.spec.js +29 -0
  25. package/src/color-mode/index.js +3 -0
  26. package/src/color-mode/resolve-theme-colors.js +18 -6
  27. package/src/color-mode/resolve-theme-colors.spec.js +13 -3
  28. package/src/color-mode/spa-navigation.js +14 -0
  29. package/src/color-mode/spa-navigation.spec.js +25 -0
  30. package/src/color-mode/use-document-color-mode-surface.js +52 -0
  31. package/src/color-mode/use-document-color-mode-surface.node.spec.js +12 -0
  32. package/src/color-mode/use-document-color-mode-surface.spec.js +154 -0
  33. package/src/color-toggle-styles.css +10 -0
  34. package/src/color-toggle.js +11 -2
  35. package/src/emotion-cache.node.spec.js +13 -0
  36. package/src/emotion-cache.spec.js +12 -0
  37. package/src/external-link-icon.js +30 -0
  38. package/src/external-link-icon.spec.js +16 -0
  39. package/src/gatsby/build-theme-ui-color-mode-head-components.js +7 -1
  40. package/src/gatsby/index.spec.js +42 -0
  41. package/src/gatsby/on-route-update-color-mode.js +1 -14
  42. package/src/header.js +4 -16
  43. package/src/lazy-load.js +30 -11
  44. package/src/lazy-load.spec.js +9 -5
  45. package/src/metric-badge.js +10 -0
  46. package/src/metric-badge.spec.js +15 -0
  47. package/src/metric-card.js +95 -0
  48. package/src/metric-card.spec.js +60 -0
  49. package/src/muted-card-footer.js +22 -0
  50. package/src/muted-card-footer.spec.js +25 -0
  51. package/src/next/app-shell.js +34 -0
  52. package/src/next/emotion-registry.js +68 -0
  53. package/src/next/emotion-registry.spec.js +99 -0
  54. package/src/next/index.js +4 -0
  55. package/src/next/root-layout-head.js +42 -0
  56. package/src/next/root-layout-head.spec.js +17 -0
  57. package/src/next/theme-ui-color-mode-route-sync.js +32 -0
  58. package/src/page-backdrop.js +42 -0
  59. package/src/page-backdrop.spec.js +41 -0
  60. package/src/pagination-button.js +4 -4
  61. package/src/pagination-button.spec.js +26 -2
  62. package/src/pagination.js +198 -0
  63. package/src/pagination.spec.js +281 -0
  64. package/src/skip-nav/SkipNavLink.js +6 -5
  65. package/src/skip-nav/SkipNavLink.spec.js +11 -0
  66. package/src/status-card.js +18 -0
  67. package/src/status-card.spec.js +38 -0
  68. package/src/theme.js +27 -20
  69. package/src/widget-call-to-action.js +106 -0
  70. package/src/widget-call-to-action.spec.js +115 -0
  71. package/src/widget-section.js +83 -0
  72. package/src/widget-section.spec.js +59 -0
@@ -0,0 +1,115 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { ThemeUIProvider } from 'theme-ui'
3
+
4
+ import WidgetCallToAction, { WidgetCtaLoadingIndicator } from './widget-call-to-action.js'
5
+
6
+ const LinkMock = ({ to, href, children, ...rest }) => (
7
+ <a href={href ?? to} data-testid='router-link' {...rest}>
8
+ {children}
9
+ </a>
10
+ )
11
+
12
+ const theme = { links: { widgetCta: { color: 'text' } } }
13
+
14
+ const wrap = ui => render(<ThemeUIProvider theme={theme}>{ui}</ThemeUIProvider>)
15
+
16
+ describe('WidgetCtaLoadingIndicator', () => {
17
+ it('renders svg', () => {
18
+ const { container } = wrap(<WidgetCtaLoadingIndicator />)
19
+ expect(container.querySelector('svg')).toBeInTheDocument()
20
+ })
21
+ })
22
+
23
+ describe('WidgetCallToAction', () => {
24
+ it('renders external link with url', () => {
25
+ wrap(
26
+ <WidgetCallToAction title='t' url='https://example.com'>
27
+ Go
28
+ </WidgetCallToAction>
29
+ )
30
+ const a = screen.getByRole('link')
31
+ expect(a).toHaveAttribute('href', 'https://example.com')
32
+ })
33
+
34
+ it('prefers href over url', () => {
35
+ wrap(
36
+ <WidgetCallToAction href='https://a.test' url='https://b.test'>
37
+ Go
38
+ </WidgetCallToAction>
39
+ )
40
+ expect(screen.getByRole('link')).toHaveAttribute('href', 'https://a.test')
41
+ })
42
+
43
+ it('uses linkComponent with to (Gatsby-style)', () => {
44
+ wrap(
45
+ <WidgetCallToAction linkComponent={LinkMock} to='/blog'>
46
+ Posts
47
+ </WidgetCallToAction>
48
+ )
49
+ expect(screen.getByTestId('router-link')).toHaveAttribute('href', '/blog')
50
+ })
51
+
52
+ it('uses linkComponent with href (Next.js-style)', () => {
53
+ wrap(
54
+ <WidgetCallToAction linkComponent={LinkMock} href='/dashboard'>
55
+ Dash
56
+ </WidgetCallToAction>
57
+ )
58
+ expect(screen.getByTestId('router-link')).toHaveAttribute('href', '/dashboard')
59
+ })
60
+
61
+ it('uses linkComponent with to when href is empty', () => {
62
+ wrap(
63
+ <WidgetCallToAction linkComponent={LinkMock} href='' to='/blog'>
64
+ Posts
65
+ </WidgetCallToAction>
66
+ )
67
+ expect(screen.getByTestId('router-link')).toHaveAttribute('href', '/blog')
68
+ })
69
+
70
+ it('ignores linkComponent for external url when no href or to', () => {
71
+ wrap(
72
+ <WidgetCallToAction linkComponent={LinkMock} url='https://example.com/out'>
73
+ Out
74
+ </WidgetCallToAction>
75
+ )
76
+ expect(screen.queryByTestId('router-link')).not.toBeInTheDocument()
77
+ expect(screen.getByRole('link')).toHaveAttribute('href', 'https://example.com/out')
78
+ })
79
+
80
+ it('uses plain anchor when to without linkComponent', () => {
81
+ wrap(
82
+ <WidgetCallToAction to='/path' linkComponent={undefined}>
83
+ P
84
+ </WidgetCallToAction>
85
+ )
86
+ expect(screen.getByRole('link')).toHaveAttribute('href', '/path')
87
+ })
88
+
89
+ it('shows loading indicator', () => {
90
+ const { container } = wrap(
91
+ <WidgetCallToAction isLoading title='t'>
92
+ X
93
+ </WidgetCallToAction>
94
+ )
95
+ expect(container.querySelector('svg')).toBeInTheDocument()
96
+ })
97
+
98
+ it('uses custom loadingSlot', () => {
99
+ wrap(
100
+ <WidgetCallToAction isLoading loadingSlot={<span data-testid='ld'>wait</span>}>
101
+ X
102
+ </WidgetCallToAction>
103
+ )
104
+ expect(screen.getByTestId('ld')).toBeInTheDocument()
105
+ })
106
+
107
+ it('merges sx', () => {
108
+ wrap(
109
+ <WidgetCallToAction href='https://x.test' sx={{ m: 2 }}>
110
+ L
111
+ </WidgetCallToAction>
112
+ )
113
+ expect(screen.getByRole('link')).toBeInTheDocument()
114
+ })
115
+ })
@@ -0,0 +1,83 @@
1
+ import React from 'react'
2
+ import { Box } from '@theme-ui/components'
3
+ import { useThemeUI } from 'theme-ui'
4
+
5
+ import isDarkMode from './helpers/isDarkMode.js'
6
+
7
+ const sectionSx = {
8
+ mb: 4,
9
+ pt: [0, 3, 4],
10
+ pb: [0, 3, 4]
11
+ }
12
+
13
+ /**
14
+ * Wraps a dashboard widget: vertical spacing and optional fatal-error overlay.
15
+ */
16
+ const WidgetSection = ({ children, hasFatalError, id, styleOverrides = {}, ...props }) => {
17
+ const { colorMode } = useThemeUI()
18
+ const darkMode = isDarkMode(colorMode)
19
+
20
+ return (
21
+ <Box
22
+ as='section'
23
+ sx={{
24
+ ...sectionSx,
25
+ ...styleOverrides,
26
+ ...(hasFatalError
27
+ ? {
28
+ position: 'relative'
29
+ }
30
+ : {})
31
+ }}
32
+ {...(id ? { id } : {})}
33
+ {...props}
34
+ >
35
+ {hasFatalError && (
36
+ <Box
37
+ sx={{
38
+ alignItems: 'center',
39
+ bottom: 0,
40
+ display: 'flex',
41
+ justifyContent: 'center',
42
+ left: 0,
43
+ position: 'absolute',
44
+ right: 0,
45
+ top: 0
46
+ }}
47
+ >
48
+ <Box
49
+ sx={{
50
+ background: darkMode ? '#252e3c' : 'white',
51
+ borderLeft: '2px solid red',
52
+ borderRight: '2px solid red',
53
+ borderRadius: '2px',
54
+ boxShadow: 'xl',
55
+ py: 3,
56
+ px: 4,
57
+ zIndex: 480
58
+ }}
59
+ >
60
+ <h4>Something went wrong</h4>
61
+ <p>Failed to load this widget.</p>
62
+ </Box>
63
+ <Box
64
+ sx={{
65
+ top: 0,
66
+ right: 0,
67
+ bottom: 0,
68
+ left: 0,
69
+ background: darkMode
70
+ ? 'radial-gradient(rgba(14.5,18,23.5,0.4) 20%, transparent 50%);'
71
+ : 'radial-gradient(rgba(255, 255, 255, 0.4) 20%, transparent 50%)',
72
+ position: 'absolute',
73
+ zIndex: 470
74
+ }}
75
+ />
76
+ </Box>
77
+ )}
78
+ {children}
79
+ </Box>
80
+ )
81
+ }
82
+
83
+ export default WidgetSection
@@ -0,0 +1,59 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { ThemeUIProvider } from 'theme-ui'
3
+
4
+ import WidgetSection from './widget-section.js'
5
+
6
+ jest.mock('theme-ui', () => {
7
+ const actual = jest.requireActual('theme-ui')
8
+ return {
9
+ ...actual,
10
+ useThemeUI: jest.fn(() => ({ colorMode: 'default' }))
11
+ }
12
+ })
13
+
14
+ const { useThemeUI } = require('theme-ui')
15
+
16
+ describe('WidgetSection', () => {
17
+ it('renders children', () => {
18
+ useThemeUI.mockReturnValue({ colorMode: 'default' })
19
+ render(
20
+ <ThemeUIProvider theme={{}}>
21
+ <WidgetSection>
22
+ <span>content</span>
23
+ </WidgetSection>
24
+ </ThemeUIProvider>
25
+ )
26
+ expect(screen.getByText('content')).toBeInTheDocument()
27
+ })
28
+
29
+ it('sets id when provided', () => {
30
+ useThemeUI.mockReturnValue({ colorMode: 'default' })
31
+ render(
32
+ <ThemeUIProvider theme={{}}>
33
+ <WidgetSection id='w1'>x</WidgetSection>
34
+ </ThemeUIProvider>
35
+ )
36
+ expect(document.getElementById('w1')).toBeTruthy()
37
+ })
38
+
39
+ it('shows fatal error overlay', () => {
40
+ useThemeUI.mockReturnValue({ colorMode: 'dark' })
41
+ render(
42
+ <ThemeUIProvider theme={{}}>
43
+ <WidgetSection hasFatalError>inside</WidgetSection>
44
+ </ThemeUIProvider>
45
+ )
46
+ expect(screen.getByText('Something went wrong')).toBeInTheDocument()
47
+ expect(screen.getByText('inside')).toBeInTheDocument()
48
+ })
49
+
50
+ it('uses light overlay colors in light mode', () => {
51
+ useThemeUI.mockReturnValue({ colorMode: 'default' })
52
+ render(
53
+ <ThemeUIProvider theme={{}}>
54
+ <WidgetSection hasFatalError />
55
+ </ThemeUIProvider>
56
+ )
57
+ expect(screen.getByText('Something went wrong')).toBeInTheDocument()
58
+ })
59
+ })