@chronogrove/ui 0.76.0 → 0.78.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 (54) hide show
  1. package/README.md +18 -11
  2. package/jest.config.cjs +1 -0
  3. package/package.json +35 -1
  4. package/src/__snapshots__/header.spec.js.snap +69 -0
  5. package/src/__snapshots__/page-header.spec.js.snap +11 -0
  6. package/src/action-button.js +83 -0
  7. package/src/action-button.spec.js +201 -0
  8. package/src/color-utils.js +37 -0
  9. package/src/color-utils.spec.js +43 -0
  10. package/src/gatsby/build-theme-ui-color-mode-head-components.js +36 -0
  11. package/src/gatsby/index.js +4 -0
  12. package/src/gatsby/index.spec.js +131 -0
  13. package/src/gatsby/on-pre-render-html-sort.js +21 -0
  14. package/src/gatsby/on-route-update-color-mode.js +14 -0
  15. package/src/header.js +28 -0
  16. package/src/header.spec.js +47 -0
  17. package/src/lazy-load.js +41 -0
  18. package/src/lazy-load.spec.js +88 -0
  19. package/src/page-header.js +10 -0
  20. package/src/page-header.spec.js +17 -0
  21. package/src/pagination-button.js +106 -0
  22. package/src/pagination-button.spec.js +197 -0
  23. package/.turbo/turbo-test$colon$coverage.log +0 -44
  24. package/.turbo/turbo-test.log +0 -168
  25. package/coverage/clover.xml +0 -131
  26. package/coverage/coverage-final.json +0 -13
  27. package/coverage/lcov-report/base.css +0 -224
  28. package/coverage/lcov-report/block-navigation.js +0 -87
  29. package/coverage/lcov-report/browser-sync.js.html +0 -268
  30. package/coverage/lcov-report/favicon.png +0 -0
  31. package/coverage/lcov-report/index.html +0 -161
  32. package/coverage/lcov-report/prettify.css +0 -1
  33. package/coverage/lcov-report/prettify.js +0 -2
  34. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  35. package/coverage/lcov-report/sorter.js +0 -210
  36. package/coverage/lcov-report/src/button.js.html +0 -160
  37. package/coverage/lcov-report/src/color-mode/browser-sync.js.html +0 -268
  38. package/coverage/lcov-report/src/color-mode/constants.js.html +0 -121
  39. package/coverage/lcov-report/src/color-mode/head-inline.js.html +0 -304
  40. package/coverage/lcov-report/src/color-mode/index.html +0 -176
  41. package/coverage/lcov-report/src/color-mode/index.js.html +0 -124
  42. package/coverage/lcov-report/src/color-mode/normalize.js.html +0 -112
  43. package/coverage/lcov-report/src/color-mode/resolve-theme-colors.js.html +0 -154
  44. package/coverage/lcov-report/src/color-toggle.js.html +0 -142
  45. package/coverage/lcov-report/src/emotion-cache.js.html +0 -151
  46. package/coverage/lcov-report/src/helpers/index.html +0 -116
  47. package/coverage/lcov-report/src/helpers/isDarkMode.js.html +0 -91
  48. package/coverage/lcov-report/src/index.html +0 -161
  49. package/coverage/lcov-report/src/provider.js.html +0 -124
  50. package/coverage/lcov-report/src/skip-nav/SkipNavContent.js.html +0 -133
  51. package/coverage/lcov-report/src/skip-nav/SkipNavLink.js.html +0 -301
  52. package/coverage/lcov-report/src/skip-nav/index.html +0 -131
  53. package/coverage/lcov-report/src/theme.js.html +0 -2143
  54. package/coverage/lcov.info +0 -309
@@ -0,0 +1,131 @@
1
+ import React from 'react'
2
+ import { render } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+
5
+ import chronogroveTheme from '../theme.js'
6
+ import * as gatsby from './index.js'
7
+ import { buildThemeUiColorModeHeadComponents } from './build-theme-ui-color-mode-head-components.js'
8
+ import { onPreRenderHTMLSortThemeUiColorModeFirst } from './on-pre-render-html-sort.js'
9
+ import { onRouteUpdateThemeUiColorMode } from './on-route-update-color-mode.js'
10
+
11
+ describe('@chronogrove/ui/gatsby public API', () => {
12
+ it('exports reconcile event and helpers from the barrel', () => {
13
+ expect(gatsby.RECONCILE_COLOR_MODE_EVENT).toBe('theme-ui-reconcile-color-mode')
14
+ expect(gatsby.buildThemeUiColorModeHeadComponents).toEqual(expect.any(Function))
15
+ expect(gatsby.onPreRenderHTMLSortThemeUiColorModeFirst).toEqual(expect.any(Function))
16
+ expect(gatsby.onRouteUpdateThemeUiColorMode).toEqual(expect.any(Function))
17
+ })
18
+ })
19
+
20
+ describe('@chronogrove/ui/gatsby', () => {
21
+ describe('buildThemeUiColorModeHeadComponents', () => {
22
+ it('returns no-flash script, HTML background script, and fallback style for the theme', () => {
23
+ const head = buildThemeUiColorModeHeadComponents({ theme: chronogroveTheme })
24
+ expect(head).toHaveLength(3)
25
+ expect(head[0].key).toBe('theme-ui-no-flash')
26
+ expect(head[1].key).toBe('html-bg-color')
27
+ expect(head[2].key).toBe('theme-ui-color-mode-fallback')
28
+
29
+ const { container: colorModeScriptContainer } = render(head[0])
30
+ const colorModeScriptTag = colorModeScriptContainer.querySelector('script')
31
+ expect(colorModeScriptTag).toHaveTextContent(/localStorage\.getItem\(['"]theme-ui-color-mode['"]\)/)
32
+ expect(colorModeScriptTag).toHaveTextContent(/data-theme-ui-color-mode/)
33
+
34
+ const { container: htmlBgScriptContainer } = render(head[1])
35
+ const htmlBgScriptTag = htmlBgScriptContainer.querySelector('script')
36
+ expect(htmlBgScriptTag).toHaveTextContent(/#14141F/)
37
+ expect(htmlBgScriptTag).toHaveTextContent(/#fdf8f5/)
38
+
39
+ const { container: fallbackStyleContainer } = render(head[2])
40
+ const fallbackStyle = fallbackStyleContainer.querySelector('style')
41
+ expect(fallbackStyle).toHaveTextContent(/:root\[data-theme-ui-color-mode="default"\]/)
42
+ expect(fallbackStyle).toHaveTextContent(/--theme-ui-colors-text: #111 !important/)
43
+ })
44
+ })
45
+
46
+ describe('onPreRenderHTMLSortThemeUiColorModeFirst', () => {
47
+ it('puts color-mode scripts and fallback style before other head components', () => {
48
+ const getHeadComponents = jest.fn(() => [
49
+ { key: 'emotion-insertion-point', type: 'meta' },
50
+ { key: 'theme-ui-no-flash', type: 'script' },
51
+ { key: 'html-bg-color', type: 'script' },
52
+ { key: 'theme-ui-color-mode-fallback', type: 'style' }
53
+ ])
54
+ const replaceHeadComponents = jest.fn()
55
+
56
+ onPreRenderHTMLSortThemeUiColorModeFirst({ getHeadComponents, replaceHeadComponents })
57
+
58
+ const sorted = replaceHeadComponents.mock.calls[0][0]
59
+ const colorModeKeys = ['theme-ui-no-flash', 'html-bg-color', 'theme-ui-color-mode-fallback']
60
+ const firstThree = sorted.slice(0, 3).map(c => c.key)
61
+ colorModeKeys.forEach(key => expect(firstThree).toContain(key))
62
+ expect(sorted[3].key).toBe('emotion-insertion-point')
63
+ })
64
+
65
+ it('sorts color-mode keys before arbitrary head components', () => {
66
+ const getHeadComponents = jest.fn(() => [
67
+ { key: 'other-meta', type: 'meta' },
68
+ { key: 'theme-ui-no-flash', type: 'script' },
69
+ { key: 'another-tag', type: 'link' },
70
+ { key: 'html-bg-color', type: 'script' },
71
+ { key: 'theme-ui-color-mode-fallback', type: 'style' }
72
+ ])
73
+ const replaceHeadComponents = jest.fn()
74
+
75
+ onPreRenderHTMLSortThemeUiColorModeFirst({ getHeadComponents, replaceHeadComponents })
76
+
77
+ const sorted = replaceHeadComponents.mock.calls[0][0]
78
+ const keys = sorted.map(c => c.key)
79
+ expect(keys.indexOf('theme-ui-no-flash')).toBeLessThan(keys.indexOf('other-meta'))
80
+ expect(keys.indexOf('html-bg-color')).toBeLessThan(keys.indexOf('another-tag'))
81
+ expect(keys.indexOf('theme-ui-color-mode-fallback')).toBeLessThan(keys.indexOf('other-meta'))
82
+ })
83
+
84
+ it('preserves relative order among non-priority head components (comparator returns 0)', () => {
85
+ const getHeadComponents = jest.fn(() => [
86
+ { key: 'z-last', type: 'meta' },
87
+ { key: 'a-first', type: 'meta' }
88
+ ])
89
+ const replaceHeadComponents = jest.fn()
90
+ onPreRenderHTMLSortThemeUiColorModeFirst({ getHeadComponents, replaceHeadComponents })
91
+ expect(replaceHeadComponents.mock.calls[0][0].map(c => c.key)).toEqual(['z-last', 'a-first'])
92
+ })
93
+
94
+ it('preserves relative order among priority keys when both sides are priority (comparator returns 0)', () => {
95
+ const getHeadComponents = jest.fn(() => [
96
+ { key: 'html-bg-color', type: 'script' },
97
+ { key: 'theme-ui-no-flash', type: 'script' }
98
+ ])
99
+ const replaceHeadComponents = jest.fn()
100
+ onPreRenderHTMLSortThemeUiColorModeFirst({ getHeadComponents, replaceHeadComponents })
101
+ expect(replaceHeadComponents.mock.calls[0][0].map(c => c.key)).toEqual(['html-bg-color', 'theme-ui-no-flash'])
102
+ })
103
+ })
104
+
105
+ describe('onRouteUpdateThemeUiColorMode', () => {
106
+ beforeEach(() => {
107
+ window.localStorage.removeItem('theme-ui-color-mode')
108
+ document.documentElement.removeAttribute('data-theme-ui-color-mode')
109
+ document.documentElement.className = ''
110
+ })
111
+
112
+ it('syncs DOM from localStorage and dispatches reconcile event', () => {
113
+ window.localStorage.setItem('theme-ui-color-mode', 'dark')
114
+ const listener = jest.fn()
115
+ window.addEventListener('theme-ui-reconcile-color-mode', listener)
116
+
117
+ onRouteUpdateThemeUiColorMode()
118
+
119
+ expect(document.documentElement.getAttribute('data-theme-ui-color-mode')).toBe('dark')
120
+ expect(listener).toHaveBeenCalled()
121
+ })
122
+
123
+ it('does not throw when CustomEvent is unavailable', () => {
124
+ const original = window.CustomEvent
125
+ window.CustomEvent = undefined
126
+ window.localStorage.setItem('theme-ui-color-mode', 'default')
127
+ expect(() => onRouteUpdateThemeUiColorMode()).not.toThrow()
128
+ window.CustomEvent = original
129
+ })
130
+ })
131
+ })
@@ -0,0 +1,21 @@
1
+ import { CHRONOGROVE_COLOR_MODE_HEAD_PRIORITY_KEYS } from '../color-mode/constants.js'
2
+
3
+ /**
4
+ * Gatsby `onPreRenderHTML`: move Theme UI color-mode head entries (`theme-ui-no-flash`, `html-bg-color`,
5
+ * `theme-ui-color-mode-fallback`) before other head components to minimize flash and ordering issues.
6
+ *
7
+ * @param {{ getHeadComponents: () => unknown[], replaceHeadComponents: (c: unknown[]) => void }} api
8
+ */
9
+ export function onPreRenderHTMLSortThemeUiColorModeFirst({ getHeadComponents, replaceHeadComponents }) {
10
+ const headComponents = getHeadComponents()
11
+ const priorityKeys = CHRONOGROVE_COLOR_MODE_HEAD_PRIORITY_KEYS
12
+ const sorted = [...headComponents].sort((a, b) => {
13
+ const aKey = a?.key ?? ''
14
+ const bKey = b?.key ?? ''
15
+ const aFirst = priorityKeys.includes(aKey) ? -1 : 0
16
+ const bFirst = priorityKeys.includes(bKey) ? -1 : 0
17
+ if (aFirst !== bFirst) return aFirst - bFirst
18
+ return 0
19
+ })
20
+ replaceHeadComponents(sorted)
21
+ }
@@ -0,0 +1,14 @@
1
+ import { RECONCILE_COLOR_MODE_EVENT } from '../color-mode/constants.js'
2
+ import { scheduleThemeUiColorModeSync } from '../color-mode/browser-sync.js'
3
+
4
+ /**
5
+ * Call from Gatsby `onRouteUpdate` so Theme UI color mode stays aligned with `localStorage` and the
6
+ * document after client-side navigations. Dispatches {@link RECONCILE_COLOR_MODE_EVENT} for app code
7
+ * that listens (e.g. React context reconciliation).
8
+ */
9
+ export function onRouteUpdateThemeUiColorMode() {
10
+ scheduleThemeUiColorModeSync()
11
+ if (typeof window !== 'undefined' && typeof window.CustomEvent === 'function') {
12
+ window.dispatchEvent(new window.CustomEvent(RECONCILE_COLOR_MODE_EVENT))
13
+ }
14
+ }
package/src/header.js ADDED
@@ -0,0 +1,28 @@
1
+ /** @jsx jsx */
2
+ import { jsx } from 'theme-ui'
3
+
4
+ /**
5
+ * Header
6
+ *
7
+ * A decorative masthead element that can be used across page layouts.
8
+ */
9
+ const Header = ({ children, styles }) => {
10
+ return (
11
+ <header
12
+ role='banner'
13
+ sx={{
14
+ variant: 'styles.Header'
15
+ }}
16
+ >
17
+ <div
18
+ sx={{
19
+ ...(styles ? styles : {})
20
+ }}
21
+ >
22
+ {children}
23
+ </div>
24
+ </header>
25
+ )
26
+ }
27
+
28
+ export default Header
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { render } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+
5
+ import Header from './header.js'
6
+
7
+ describe('Header', () => {
8
+ it('renders with children', () => {
9
+ const { asFragment } = render(
10
+ <Header>
11
+ <h1>Test Header</h1>
12
+ </Header>
13
+ )
14
+ expect(asFragment()).toMatchSnapshot()
15
+ })
16
+
17
+ it('renders with custom styles', () => {
18
+ const customStyles = {
19
+ backgroundColor: 'red',
20
+ color: 'white'
21
+ }
22
+ const { asFragment } = render(
23
+ <Header styles={customStyles}>
24
+ <h1>Test Header with Styles</h1>
25
+ </Header>
26
+ )
27
+ expect(asFragment()).toMatchSnapshot()
28
+ })
29
+
30
+ it('renders without styles prop', () => {
31
+ const { asFragment } = render(
32
+ <Header>
33
+ <h1>Test Header without Styles</h1>
34
+ </Header>
35
+ )
36
+ expect(asFragment()).toMatchSnapshot()
37
+ })
38
+
39
+ it('renders with empty styles object', () => {
40
+ const { asFragment } = render(
41
+ <Header styles={{}}>
42
+ <h1>Test Header with Empty Styles</h1>
43
+ </Header>
44
+ )
45
+ expect(asFragment()).toMatchSnapshot()
46
+ })
47
+ })
@@ -0,0 +1,41 @@
1
+ /** @jsx jsx */
2
+ import { jsx } from 'theme-ui'
3
+ import { useState, useEffect } from 'react'
4
+ import { useInView } from 'react-intersection-observer'
5
+
6
+ const DefaultPlaceholder = ({ height = '100%', width = '100%' }) => (
7
+ <div
8
+ data-testid='default-placeholder'
9
+ sx={{
10
+ minHeight: '1px',
11
+ minWidth: '1px',
12
+ height,
13
+ width
14
+ }}
15
+ >
16
+ {' '}
17
+ </div>
18
+ )
19
+
20
+ /**
21
+ * Lazy Loader
22
+ *
23
+ * Hides a component until it's been visible in the viewport.
24
+ */
25
+ const LazyLoad = ({ children, placeholder = <DefaultPlaceholder /> }) => {
26
+ const [hasBeenVisible, setHasBeenVisible] = useState(false)
27
+ const { ref, inView } = useInView({
28
+ triggerOnce: true,
29
+ threshold: 0
30
+ })
31
+
32
+ useEffect(() => {
33
+ if (inView && !hasBeenVisible) {
34
+ setHasBeenVisible(true)
35
+ }
36
+ }, [inView, hasBeenVisible])
37
+
38
+ return <div ref={ref}>{hasBeenVisible ? children : placeholder}</div>
39
+ }
40
+
41
+ export default LazyLoad
@@ -0,0 +1,88 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+ import LazyLoad from './lazy-load.js'
5
+
6
+ let mockInView = false
7
+
8
+ jest.mock('react-intersection-observer', () => ({
9
+ useInView: () => ({
10
+ ref: jest.fn(),
11
+ inView: mockInView
12
+ })
13
+ }))
14
+
15
+ const MockPlaceholder = () => <div data-testid='placeholder'>Placeholder</div>
16
+
17
+ describe('LazyLoad', () => {
18
+ afterEach(() => {
19
+ jest.clearAllMocks()
20
+ mockInView = false
21
+ })
22
+
23
+ it('renders the default placeholder initially', () => {
24
+ render(
25
+ <LazyLoad>
26
+ <div data-testid='content'>Lazy Loaded Content</div>
27
+ </LazyLoad>
28
+ )
29
+
30
+ expect(screen.getByTestId('default-placeholder')).toBeInTheDocument()
31
+ expect(screen.queryByTestId('content')).not.toBeInTheDocument()
32
+ })
33
+
34
+ it('renders the children when visible', () => {
35
+ mockInView = true
36
+
37
+ render(
38
+ <LazyLoad>
39
+ <div data-testid='content'>Lazy Loaded Content</div>
40
+ </LazyLoad>
41
+ )
42
+
43
+ expect(screen.getByTestId('content')).toBeInTheDocument()
44
+ })
45
+
46
+ it('renders the custom placeholder when provided', () => {
47
+ render(
48
+ <LazyLoad placeholder={<MockPlaceholder />}>
49
+ <div data-testid='content'>Lazy Loaded Content</div>
50
+ </LazyLoad>
51
+ )
52
+
53
+ expect(screen.getByTestId('placeholder')).toBeInTheDocument()
54
+ expect(screen.queryByTestId('content')).not.toBeInTheDocument()
55
+ })
56
+
57
+ it('does not re-render children if already visible', () => {
58
+ mockInView = true
59
+
60
+ const { rerender } = render(
61
+ <LazyLoad>
62
+ <div data-testid='content'>Lazy Loaded Content</div>
63
+ </LazyLoad>
64
+ )
65
+
66
+ expect(screen.getByTestId('content')).toBeInTheDocument()
67
+
68
+ mockInView = false
69
+
70
+ rerender(
71
+ <LazyLoad>
72
+ <div data-testid='content'>Lazy Loaded Content</div>
73
+ </LazyLoad>
74
+ )
75
+
76
+ expect(screen.getByTestId('content')).toBeInTheDocument()
77
+ })
78
+
79
+ it('does not render children when visibility remains false', () => {
80
+ render(
81
+ <LazyLoad>
82
+ <div data-testid='content'>Lazy Loaded Content</div>
83
+ </LazyLoad>
84
+ )
85
+
86
+ expect(screen.queryByTestId('content')).not.toBeInTheDocument()
87
+ })
88
+ })
@@ -0,0 +1,10 @@
1
+ import React from 'react'
2
+ import { Heading } from '@theme-ui/components'
3
+
4
+ const PageHeader = ({ children }) => (
5
+ <Heading as='h1' className='p-name' sx={{ mb: 2, lineHeight: 1.5, fontSize: [6, 'calc(1.25em + 2vw)'] }}>
6
+ {children}
7
+ </Heading>
8
+ )
9
+
10
+ export default PageHeader
@@ -0,0 +1,17 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+ import PageHeader from './page-header.js'
5
+
6
+ describe('PageHeader', () => {
7
+ it('renders correctly with given children', () => {
8
+ render(<PageHeader>Hello, World!</PageHeader>)
9
+ const headingElement = screen.getByRole('heading', { name: /Hello, World!/i })
10
+ expect(headingElement).toBeInTheDocument()
11
+ })
12
+
13
+ it('matches snapshot', () => {
14
+ const { asFragment } = render(<PageHeader>Hello, World!</PageHeader>)
15
+ expect(asFragment()).toMatchSnapshot()
16
+ })
17
+ })
@@ -0,0 +1,106 @@
1
+ /** @jsx jsx */
2
+ import React from 'react'
3
+ import { jsx, useThemeUI } from 'theme-ui'
4
+ import isDarkMode from './helpers/isDarkMode.js'
5
+ import { hexToRgb } from './color-utils.js'
6
+
7
+ const PaginationButton = ({
8
+ children,
9
+ onClick,
10
+ disabled = false,
11
+ active = false,
12
+ variant = 'primary',
13
+ size = 'medium',
14
+ icon,
15
+ ...props
16
+ }) => {
17
+ const { colorMode, theme } = useThemeUI()
18
+ const darkModeActive = isDarkMode(colorMode)
19
+
20
+ const colorVariants = {
21
+ secondary: { light: '#666', dark: '#888' },
22
+ success: { light: '#28a745', dark: '#4CAF50' },
23
+ warning: { light: '#ffc107', dark: '#FF9800' },
24
+ danger: { light: '#dc3545', dark: '#f44336' }
25
+ }
26
+ const isPrimary = variant === 'primary' || !colorVariants[variant]
27
+ const primaryColor = isPrimary
28
+ ? (theme?.colors?.primary ?? '#422EA3')
29
+ : darkModeActive
30
+ ? colorVariants[variant].dark
31
+ : colorVariants[variant].light
32
+ const rgb = isPrimary ? (theme?.colors?.primaryRgb ?? '66, 46, 163') : hexToRgb(primaryColor)
33
+
34
+ const sizeVariants = {
35
+ small: {
36
+ fontSize: ['10px', '11px'],
37
+ padding: '4px 8px',
38
+ minWidth: ['24px', '28px'],
39
+ height: ['24px', '28px'],
40
+ gap: 1
41
+ },
42
+ medium: {
43
+ fontSize: ['11px', '12px'],
44
+ padding: '6px 10px',
45
+ minWidth: ['28px', '32px'],
46
+ height: ['28px', '32px'],
47
+ gap: 1
48
+ },
49
+ large: {
50
+ fontSize: ['12px', '13px'],
51
+ padding: '8px 12px',
52
+ minWidth: ['32px', '36px'],
53
+ height: ['32px', '36px'],
54
+ gap: 2
55
+ }
56
+ }
57
+ const sizeStyles = sizeVariants[size] || sizeVariants.medium
58
+
59
+ const baseStyles = {
60
+ color: active ? 'white' : primaryColor,
61
+ textDecoration: 'none',
62
+ fontWeight: active ? 'bold' : 'medium',
63
+ fontSize: sizeStyles.fontSize,
64
+ display: 'inline-flex',
65
+ alignItems: 'center',
66
+ justifyContent: 'center',
67
+ gap: sizeStyles.gap,
68
+ padding: sizeStyles.padding,
69
+ minWidth: sizeStyles.minWidth,
70
+ height: sizeStyles.height,
71
+ borderRadius: '6px',
72
+ background: active ? primaryColor : `rgba(${rgb}, 0.1)`,
73
+ border: active ? `1px solid ${primaryColor}` : `1px solid rgba(${rgb}, 0.2)`,
74
+ transition: 'all 0.2s ease',
75
+ cursor: disabled ? 'not-allowed' : 'pointer',
76
+ outline: 'none',
77
+ opacity: disabled ? 0.5 : 1,
78
+ '&:hover:not(:disabled)': {
79
+ background: active ? primaryColor : `rgba(${rgb}, 0.2)`,
80
+ textDecoration: 'none',
81
+ transform: 'scale(1.02)'
82
+ },
83
+ '&:focus:not(:disabled)': {
84
+ outline: 'none',
85
+ boxShadow: `0 0 0 2px ${primaryColor}20`
86
+ },
87
+ '&:active:not(:disabled)': {
88
+ transform: 'scale(0.98)'
89
+ }
90
+ }
91
+
92
+ const content = (
93
+ <>
94
+ {children}
95
+ {icon && icon}
96
+ </>
97
+ )
98
+
99
+ return (
100
+ <button type='button' onClick={onClick} disabled={disabled} sx={baseStyles} {...props}>
101
+ {content}
102
+ </button>
103
+ )
104
+ }
105
+
106
+ export default PaginationButton