Skip to content

Commit 8cd499d

Browse files
committed
Add tabbar to preact implementation
1 parent 9133bf0 commit 8cd499d

15 files changed

+327
-18
lines changed

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@
1212
"build:prod": "npm run dist:clear && webpack --config webpack.config.js --mode production"
1313
},
1414
"dependencies": {
15+
"@linaria/core": "6.2.0",
1516
"@linaria/react": "6.2.1",
1617
"preact": "10.25.0",
1718
"webextension-polyfill-ts": "0.26.0"
1819
},
1920
"devDependencies": {
21+
"@babel/preset-typescript": "7.26.0",
2022
"@testing-library/dom": "10.4.0",
2123
"@testing-library/react": "16.0.1",
2224
"@testing-library/user-event": "14.5.2",
2325
"@types/chrome": "0.0.283",
2426
"@types/jest": "29.5.14",
2527
"@types/node": "22.9.3",
28+
"@wyw-in-js/babel-preset": "0.5.5",
2629
"@wyw-in-js/webpack-loader": "0.5.5",
2730
"canvas": "Automattic/node-canvas#master",
2831
"copy-webpack-plugin": "12.0.2",

src/content/components/Content.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ export const Content = () => {
8686
body.style.overflowY = bodyOverflowStyles.current?.overflow ?? ''
8787

8888
bodyOverflowStyles.current = undefined
89+
90+
setOverlayImages(null)
8991
}, [])
9092

9193
useLayoutEffect(() => {

src/content/components/Overlay.tsx

+52-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,31 @@
1-
import { useRef } from 'preact/hooks'
1+
import { styled } from '@linaria/react'
2+
import { useRef, useState } from 'preact/hooks'
3+
import { ImageDiffTabs } from './tabs/ImageDiffTabs'
4+
5+
const OverlayBackdrop = styled.div`
6+
position: fixed;
7+
top: 0;
8+
left: 0;
9+
width: 100vw;
10+
height: 100vh;
11+
overflow: hidden;
12+
display: flex;
13+
align-items: center;
14+
background-color: rgba(0, 0, 0, 0.5);
15+
transition:
16+
opacity 0.25s ease-out,
17+
visibility 0.01s allow-discrete;
18+
opacity: ${(props) => (props['data-open'] ? 1 : 0)};
19+
visibility: ${(props) => (props['data-hasContent'] ? 'visible' : 'hidden')};
20+
`
21+
22+
const OverlayContainer = styled.div`
23+
width: 100%;
24+
max-height: 100vh;
25+
display: flex;
26+
flex-direction: column;
27+
align-items: center;
28+
`
229

330
type OverlayProps =
431
| { open: false }
@@ -11,5 +38,28 @@ export const Overlay = (props: OverlayProps) => {
1138
const images = useRef<[string, string] | undefined>()
1239
images.current = props.open ? props.images : images.current
1340

14-
return <div>{images.current?.join(', ')}</div>
41+
const [showContent, setShowContent] = useState(props.open)
42+
const hasOverlay = showContent || props.open
43+
44+
return (
45+
<OverlayBackdrop
46+
onTransitionEnd={(event: TransitionEvent) => {
47+
console.log(`Transition end: ${event.propertyName}`)
48+
if (props.open && event.propertyName === 'visibility') {
49+
setShowContent(true)
50+
} else if (!props.open && event.propertyName === 'opacity') {
51+
setShowContent(false)
52+
}
53+
}}
54+
data-open={props.open}
55+
data-hasContent={hasOverlay}
56+
aria-hidden={!props.open}
57+
>
58+
{hasOverlay && images.current && (
59+
<OverlayContainer>
60+
<ImageDiffTabs images={images.current} />
61+
</OverlayContainer>
62+
)}
63+
</OverlayBackdrop>
64+
)
1565
}

src/content/components/tabs/DiffImageStats.tsx

Whitespace-only changes.

src/content/components/tabs/DiffImageTabDifference.tsx

Whitespace-only changes.

src/content/components/tabs/DiffImageTabOverlay.tsx

Whitespace-only changes.

src/content/components/tabs/DiffImageTabSideBySide.tsx

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { DEFAULT_SELECTED_TAB, TAB_LABELS } from '../../constants'
2+
import { TabBar } from './TabBar'
3+
4+
type ImageDiffTabsProps = {
5+
images: [string, string]
6+
}
7+
8+
const TAB_BAR_OPTIONS = TAB_LABELS.map(([id, label]) => ({ id, label }))
9+
10+
export const ImageDiffTabs = ({ images }: ImageDiffTabsProps) => {
11+
return (
12+
<>
13+
<TabBar
14+
options={TAB_BAR_OPTIONS}
15+
defaultTab={DEFAULT_SELECTED_TAB}
16+
onChange={(tabId) => {
17+
console.log(tabId)
18+
}}
19+
/>
20+
<div>{images.join(',')}</div>
21+
</>
22+
)
23+
}
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { styled } from '@linaria/react'
2+
import { useState } from 'preact/hooks'
3+
import {
4+
ACTIVE_TAB_COLOR,
5+
INACTIVE_TAB_COLOR,
6+
SECONDARY_COLOR,
7+
TAB_HEIGHT,
8+
TAB_WIDTH,
9+
TABS_RADIO_GROUP_NAME,
10+
} from '../../constants'
11+
12+
type TabBarProps<T extends string> = {
13+
options: {
14+
label: string
15+
id: T
16+
}[]
17+
defaultTab?: T
18+
onChange?: (value: T) => void
19+
}
20+
21+
const TabBarContainer = styled.div`
22+
display: flex;
23+
position: relative;
24+
background-color: #f4f5f7;
25+
box-shadow:
26+
0 0 1px 0 rgba(24, 94, 224, 0.15),
27+
0 6px 12px 0 rgba(24, 94, 224, 0.15);
28+
padding: 4px;
29+
border-radius: 4px;
30+
width: fit-content;
31+
32+
input[type='radio'] {
33+
position: fixed;
34+
top: -100px;
35+
}
36+
37+
input[type='radio']:checked + label {
38+
color: ${ACTIVE_TAB_COLOR};
39+
font-weight: 500;
40+
}
41+
42+
input[type='radio']:focus + label {
43+
outline: 2px solid ${ACTIVE_TAB_COLOR};
44+
}
45+
`
46+
47+
const TabBarLabel = styled.label`
48+
display: flex;
49+
align-items: center;
50+
justify-content: center;
51+
color: ${INACTIVE_TAB_COLOR};
52+
width: ${TAB_WIDTH};
53+
height: ${TAB_HEIGHT};
54+
font-size: 1rem;
55+
font-weight: 400;
56+
border-radius: 8px;
57+
cursor: pointer;
58+
transition: color 0.15s ease-in;
59+
z-index: 2;
60+
`
61+
62+
const TabBarSlider = styled.span`
63+
position: absolute;
64+
display: flex;
65+
width: ${TAB_WIDTH};
66+
height: ${TAB_HEIGHT};
67+
background-color: ${SECONDARY_COLOR};
68+
z-index: 1;
69+
border-radius: 8px;
70+
transition: 0.25s ease-out;
71+
transform: translateX(calc(100% * ${(props) => props['data-selectedIndex']}));
72+
`
73+
74+
export const TabBar = <T extends string>({
75+
options,
76+
defaultTab,
77+
onChange,
78+
}: TabBarProps<T>) => {
79+
const [selectedTab, setSelectedTab] = useState(defaultTab)
80+
const selectedTabIndex = options.findIndex(({ id }) => id === selectedTab)
81+
82+
return (
83+
<TabBarContainer role="tablist">
84+
{options.map((option) => (
85+
<>
86+
<input
87+
type="radio"
88+
id={option.id}
89+
name={TABS_RADIO_GROUP_NAME}
90+
checked={selectedTab === option.id}
91+
onChange={() => {
92+
setSelectedTab(option.id)
93+
onChange?.(option.id)
94+
}}
95+
/>
96+
97+
<TabBarLabel for={option.id} role="tab">
98+
{option.label}
99+
</TabBarLabel>
100+
</>
101+
))}
102+
103+
<TabBarSlider
104+
data-selectedIndex={selectedTabIndex === -1 ? 0 : selectedTabIndex}
105+
/>
106+
</TabBarContainer>
107+
)
108+
}

src/content/constants.ts

+6
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,9 @@ export enum ElementId {
3939
ImageDiffStatsAfter = 'image-diff-stats-after',
4040
ImageDiffStatsRatio = 'image-diff-stats-ratio',
4141
}
42+
43+
export const ACTIVE_TAB_COLOR = '#0052cc'
44+
export const INACTIVE_TAB_COLOR = '#42526e'
45+
export const SECONDARY_COLOR = 'rgb(235, 236, 240)'
46+
export const TAB_WIDTH = '150px'
47+
export const TAB_HEIGHT = '44px'

src/styles/global.pcss

-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,4 @@
66

77
--tab-width: 150px;
88
--tab-height: 44px;
9-
10-
--diff-overlay-opacity: 0.5;
119
}

static/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{
88
"matches": ["<all_urls>"],
99
"js": ["content.preact.js"],
10-
"css": ["styles/content.css"]
10+
"css": ["styles/content.css", "styles/content.preact.css"]
1111
}
1212
]
1313
}

webpack.config.js

+36-10
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
66

77
module.exports = {
88
entry: {
9-
content: './src/content/index.ts',
10-
'content.preact': './src/content/index.preact.tsx',
9+
content: {
10+
import: './src/content/index.ts',
11+
layer: 'html',
12+
},
13+
'content.preact': {
14+
import: './src/content/index.preact.tsx',
15+
layer: 'preact',
16+
},
1117
},
1218
devtool: 'inline-source-map',
1319
module: {
@@ -17,24 +23,41 @@ module.exports = {
1723
use: [
1824
{
1925
loader: '@wyw-in-js/webpack-loader',
26+
options: {
27+
babelOptions: {
28+
presets: ['@babel/preset-typescript'],
29+
},
30+
},
2031
},
2132
'ts-loader',
2233
],
2334
exclude: /node_modules/,
2435
},
2536
{
2637
test: /\.(pcss|css)$/,
27-
use: [
28-
MiniCssExtractPlugin.loader,
38+
loader: MiniCssExtractPlugin.loader,
39+
},
40+
{
41+
test: /\.(pcss|css)$/,
42+
oneOf: [
2943
{
30-
loader: 'css-loader',
31-
options: {
32-
modules: {
33-
localIdentName: 'diff-image-ext__[local]-[hash:base64:5]',
44+
issuerLayer: 'html',
45+
use: [
46+
{
47+
loader: 'css-loader',
48+
options: {
49+
modules: {
50+
localIdentName: 'diff-image-ext__[local]-[hash:base64:5]',
51+
},
52+
},
3453
},
35-
},
54+
'postcss-loader',
55+
],
56+
},
57+
{
58+
issuerLayer: 'preact',
59+
use: [{ loader: 'css-loader' }],
3660
},
37-
'postcss-loader',
3861
],
3962
},
4063
],
@@ -63,4 +86,7 @@ module.exports = {
6386
patterns: [{ from: 'static' }],
6487
}),
6588
],
89+
experiments: {
90+
layers: true,
91+
},
6692
}

wyw-in-js.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
classNameSlug: 'diff-image-ext__[hash]',
3+
}

0 commit comments

Comments
 (0)