Skip to content

Commit 3978c42

Browse files
committed
chore: add support for copy link
1 parent 4f378c5 commit 3978c42

4 files changed

Lines changed: 324 additions & 18 deletions

File tree

public/i18n/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
},
8383
"codeOfConduct": {
8484
"title": "Code of Conduct",
85+
"copyLink": "Copy link to this section",
86+
"tableOfContents": "Table of Contents",
8587
"purpose": {
8688
"title": "1. Purpose",
8789
"description1": "Web Dev Talks is an open community whose objective is to foster learning, networking, and professional growth within the technology field, in a safe, respectful, and inclusive environment for all people.",

public/i18n/es.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@
9191
},
9292
"codeOfConduct": {
9393
"title": "Código de Conducta",
94+
"copyLink": "Copiar enlace a esta sección",
95+
"tableOfContents": "Tabla de contenidos",
9496
"purpose": {
9597
"title": "1. Propósito",
9698
"description1": "Web Dev Talks es una comunidad abierta cuyo objetivo es fomentar el aprendizaje, el networking y el crecimiento profesional dentro del área de tecnología, en un ambiente seguro, respetuoso e inclusivo para todas las personas.",

src/CodeOfConduct.tsx

Lines changed: 273 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,280 @@
1-
import { Typography, Container, List, ListItem, ListItemText, ListItemIcon, Divider, Box } from '@mui/material'
1+
import { Typography, Container, List, ListItem, ListItemText, ListItemIcon, Divider, Box, IconButton, Tooltip } from '@mui/material'
22
import CircleIcon from '@mui/icons-material/Circle'
3+
import LinkIcon from '@mui/icons-material/Link'
34
import { useTranslation } from 'react-i18next'
45
import NavBar from './NavBar'
56
import Footer from './Footer'
7+
import { useAnchorLinks } from './hooks/useAnchorLinks'
68

79
function CodeOfConduct() {
810
const { t } = useTranslation()
11+
const { copyAnchorLink, scrollToElement } = useAnchorLinks()
12+
13+
// Component for section header with anchor link
14+
const SectionHeader = ({ id, variant, children, color = 'primary.main' }: {
15+
id: string,
16+
variant: 'h1' | 'h2',
17+
children: React.ReactNode,
18+
color?: string
19+
}) => (
20+
<Box
21+
id={id}
22+
sx={{
23+
display: 'flex',
24+
alignItems: 'center',
25+
gap: 1,
26+
mb: 2,
27+
'&:hover .anchor-link': {
28+
opacity: 0.6
29+
}
30+
}}
31+
>
32+
<Tooltip title={t("codeOfConduct.copyLink") || "Copiar enlace"}>
33+
<IconButton
34+
className="anchor-link"
35+
size="small"
36+
onClick={() => copyAnchorLink(id)}
37+
sx={{
38+
opacity: 0,
39+
transition: 'opacity 0.2s ease-in-out',
40+
'&:hover': { opacity: 1 },
41+
color: color,
42+
mr: 0.5
43+
}}
44+
>
45+
<LinkIcon fontSize="small" />
46+
</IconButton>
47+
</Tooltip>
48+
<Typography variant={variant} sx={{ color, flex: 1 }}>
49+
{children}
50+
</Typography>
51+
</Box>
52+
)
953

1054
return (
1155
<>
1256
<NavBar />
13-
<Container sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', pt: 5, pb: 5 }}>
57+
<Container sx={{
58+
display: 'flex',
59+
flexDirection: 'column',
60+
alignItems: 'center',
61+
pt: 5,
62+
pb: 5,
63+
// Add scroll padding to account for sticky header
64+
'& [id]': {
65+
scrollMarginTop: '80px' // 65px header + 15px extra padding
66+
}
67+
}}>
1468
<Typography variant="h1" sx={{ mb: 4, textAlign: 'center' }}>
1569
{t("codeOfConduct.title")}
1670
</Typography>
1771

72+
{/* Tabla de contenidos */}
73+
<Box sx={{ width: '100%', mb: 4, p: 3, bgcolor: 'grey.50', borderRadius: 2, border: '1px solid', borderColor: 'grey.200' }}>
74+
<Typography variant="h3" sx={{ mb: 2, color: 'primary.main' }}>
75+
{t("codeOfConduct.tableOfContents") || "Tabla de contenidos"}
76+
</Typography>
77+
<List dense>
78+
<ListItem sx={{ py: 0.5 }}>
79+
<ListItemIcon sx={{ minWidth: '20px', color: 'primary.main' }}>
80+
<CircleIcon sx={{ fontSize: '6px' }} />
81+
</ListItemIcon>
82+
<ListItemText>
83+
<Typography
84+
component="button"
85+
onClick={() => scrollToElement('proposito')}
86+
sx={{
87+
textDecoration: 'none',
88+
color: 'primary.main',
89+
background: 'none',
90+
border: 'none',
91+
cursor: 'pointer',
92+
textAlign: 'left',
93+
padding: 0,
94+
font: 'inherit',
95+
'&:hover': { textDecoration: 'underline' }
96+
}}
97+
>
98+
{t("codeOfConduct.purpose.title")}
99+
</Typography>
100+
</ListItemText>
101+
</ListItem>
102+
<ListItem sx={{ py: 0.5 }}>
103+
<ListItemIcon sx={{ minWidth: '20px', color: 'primary.main' }}>
104+
<CircleIcon sx={{ fontSize: '6px' }} />
105+
</ListItemIcon>
106+
<ListItemText>
107+
<Typography
108+
component="button"
109+
onClick={() => scrollToElement('valores')}
110+
sx={{
111+
textDecoration: 'none',
112+
color: 'primary.main',
113+
background: 'none',
114+
border: 'none',
115+
cursor: 'pointer',
116+
textAlign: 'left',
117+
padding: 0,
118+
font: 'inherit',
119+
'&:hover': { textDecoration: 'underline' }
120+
}}
121+
>
122+
{t("codeOfConduct.values.title")}
123+
</Typography>
124+
</ListItemText>
125+
</ListItem>
126+
<ListItem sx={{ py: 0.5 }}>
127+
<ListItemIcon sx={{ minWidth: '20px', color: 'primary.main' }}>
128+
<CircleIcon sx={{ fontSize: '6px' }} />
129+
</ListItemIcon>
130+
<ListItemText>
131+
<Typography
132+
component="button"
133+
onClick={() => scrollToElement('conducta-esperada')}
134+
sx={{
135+
textDecoration: 'none',
136+
color: 'primary.main',
137+
background: 'none',
138+
border: 'none',
139+
cursor: 'pointer',
140+
textAlign: 'left',
141+
padding: 0,
142+
font: 'inherit',
143+
'&:hover': { textDecoration: 'underline' }
144+
}}
145+
>
146+
{t("codeOfConduct.expected.title")}
147+
</Typography>
148+
</ListItemText>
149+
</ListItem>
150+
<ListItem sx={{ py: 0.5 }}>
151+
<ListItemIcon sx={{ minWidth: '20px', color: 'error.main' }}>
152+
<CircleIcon sx={{ fontSize: '6px' }} />
153+
</ListItemIcon>
154+
<ListItemText>
155+
<Typography
156+
component="button"
157+
onClick={() => scrollToElement('conducta-inaceptable')}
158+
sx={{
159+
textDecoration: 'none',
160+
color: 'error.main',
161+
background: 'none',
162+
border: 'none',
163+
cursor: 'pointer',
164+
textAlign: 'left',
165+
padding: 0,
166+
font: 'inherit',
167+
'&:hover': { textDecoration: 'underline' }
168+
}}
169+
>
170+
{t("codeOfConduct.unacceptable.title")}
171+
</Typography>
172+
</ListItemText>
173+
</ListItem>
174+
<ListItem sx={{ py: 0.5 }}>
175+
<ListItemIcon sx={{ minWidth: '20px', color: 'primary.main' }}>
176+
<CircleIcon sx={{ fontSize: '6px' }} />
177+
</ListItemIcon>
178+
<ListItemText>
179+
<Typography
180+
component="button"
181+
onClick={() => scrollToElement('alcance-fuera-evento')}
182+
sx={{
183+
textDecoration: 'none',
184+
color: 'primary.main',
185+
background: 'none',
186+
border: 'none',
187+
cursor: 'pointer',
188+
textAlign: 'left',
189+
padding: 0,
190+
font: 'inherit',
191+
'&:hover': { textDecoration: 'underline' }
192+
}}
193+
>
194+
{t("codeOfConduct.scope.title")}
195+
</Typography>
196+
</ListItemText>
197+
</ListItem>
198+
<ListItem sx={{ py: 0.5 }}>
199+
<ListItemIcon sx={{ minWidth: '20px', color: 'primary.main' }}>
200+
<CircleIcon sx={{ fontSize: '6px' }} />
201+
</ListItemIcon>
202+
<ListItemText>
203+
<Typography
204+
component="button"
205+
onClick={() => scrollToElement('cumplimiento-medidas')}
206+
sx={{
207+
textDecoration: 'none',
208+
color: 'primary.main',
209+
background: 'none',
210+
border: 'none',
211+
cursor: 'pointer',
212+
textAlign: 'left',
213+
padding: 0,
214+
font: 'inherit',
215+
'&:hover': { textDecoration: 'underline' }
216+
}}
217+
>
218+
{t("codeOfConduct.enforcement.title")}
219+
</Typography>
220+
</ListItemText>
221+
</ListItem>
222+
<ListItem sx={{ py: 0.5 }}>
223+
<ListItemIcon sx={{ minWidth: '20px', color: 'primary.main' }}>
224+
<CircleIcon sx={{ fontSize: '6px' }} />
225+
</ListItemIcon>
226+
<ListItemText>
227+
<Typography
228+
component="button"
229+
onClick={() => scrollToElement('reporte-incidentes')}
230+
sx={{
231+
textDecoration: 'none',
232+
color: 'primary.main',
233+
background: 'none',
234+
border: 'none',
235+
cursor: 'pointer',
236+
textAlign: 'left',
237+
padding: 0,
238+
font: 'inherit',
239+
'&:hover': { textDecoration: 'underline' }
240+
}}
241+
>
242+
{t("codeOfConduct.reporting.title")}
243+
</Typography>
244+
</ListItemText>
245+
</ListItem>
246+
<ListItem sx={{ py: 0.5 }}>
247+
<ListItemIcon sx={{ minWidth: '20px', color: 'primary.main' }}>
248+
<CircleIcon sx={{ fontSize: '6px' }} />
249+
</ListItemIcon>
250+
<ListItemText>
251+
<Typography
252+
component="button"
253+
onClick={() => scrollToElement('compromiso')}
254+
sx={{
255+
textDecoration: 'none',
256+
color: 'primary.main',
257+
background: 'none',
258+
border: 'none',
259+
cursor: 'pointer',
260+
textAlign: 'left',
261+
padding: 0,
262+
font: 'inherit',
263+
'&:hover': { textDecoration: 'underline' }
264+
}}
265+
>
266+
{t("codeOfConduct.commitment.title")}
267+
</Typography>
268+
</ListItemText>
269+
</ListItem>
270+
</List>
271+
</Box>
272+
18273
{/* Sección 1: Propósito */}
19274
<Box sx={{ width: '100%', mb: 4 }}>
20-
<Typography variant="h2" sx={{ mb: 2, color: 'primary.main' }}>
275+
<SectionHeader id="proposito" variant="h2">
21276
{t("codeOfConduct.purpose.title")}
22-
</Typography>
277+
</SectionHeader>
23278
<Typography sx={{ textAlign: 'justify', mb: 2 }}>
24279
{t("codeOfConduct.purpose.description1")}
25280
</Typography>
@@ -76,9 +331,9 @@ function CodeOfConduct() {
76331

77332
{/* Sección 2: Valores */}
78333
<Box sx={{ width: '100%', mb: 4 }}>
79-
<Typography variant="h2" sx={{ mb: 2, color: 'primary.main' }}>
334+
<SectionHeader id="valores" variant="h2">
80335
{t("codeOfConduct.values.title")}
81-
</Typography>
336+
</SectionHeader>
82337
<Typography sx={{ textAlign: 'justify', mb: 2 }}>
83338
{t("codeOfConduct.values.description")}
84339
</Typography>
@@ -129,9 +384,9 @@ function CodeOfConduct() {
129384

130385
{/* Sección 3: Conducta Esperada */}
131386
<Box sx={{ width: '100%', mb: 4 }}>
132-
<Typography variant="h2" sx={{ mb: 2, color: 'primary.main' }}>
387+
<SectionHeader id="conducta-esperada" variant="h2">
133388
{t("codeOfConduct.expected.title")}
134-
</Typography>
389+
</SectionHeader>
135390
<Typography sx={{ textAlign: 'justify', mb: 2 }}>
136391
{t("codeOfConduct.expected.description")}
137392
</Typography>
@@ -179,9 +434,9 @@ function CodeOfConduct() {
179434

180435
{/* Sección 4: Conducta Inaceptable */}
181436
<Box sx={{ width: '100%', mb: 4 }}>
182-
<Typography variant="h2" sx={{ mb: 2, color: 'error.main' }}>
437+
<SectionHeader id="conducta-inaceptable" variant="h2" color="error.main">
183438
{t("codeOfConduct.unacceptable.title")}
184-
</Typography>
439+
</SectionHeader>
185440
<Typography sx={{ textAlign: 'justify', mb: 2 }}>
186441
{t("codeOfConduct.unacceptable.description")}
187442
</Typography>
@@ -256,9 +511,9 @@ function CodeOfConduct() {
256511

257512
{/* Sección 5: Alcance fuera del evento */}
258513
<Box sx={{ width: '100%', mb: 4 }}>
259-
<Typography variant="h2" sx={{ mb: 2, color: 'primary.main' }}>
514+
<SectionHeader id="alcance-fuera-evento" variant="h2">
260515
{t("codeOfConduct.scope.title")}
261-
</Typography>
516+
</SectionHeader>
262517
<Typography sx={{ textAlign: 'justify', mb: 2 }}>
263518
{t("codeOfConduct.scope.description")}
264519
</Typography>
@@ -297,9 +552,9 @@ function CodeOfConduct() {
297552

298553
{/* Sección 6: Cumplimiento y medidas */}
299554
<Box sx={{ width: '100%', mb: 4 }}>
300-
<Typography variant="h2" sx={{ mb: 2, color: 'primary.main' }}>
555+
<SectionHeader id="cumplimiento-medidas" variant="h2">
301556
{t("codeOfConduct.enforcement.title")}
302-
</Typography>
557+
</SectionHeader>
303558
<Typography sx={{ textAlign: 'justify', mb: 2 }}>
304559
{t("codeOfConduct.enforcement.description")}
305560
</Typography>
@@ -353,9 +608,9 @@ function CodeOfConduct() {
353608

354609
{/* Sección 7: Reporte de incidentes */}
355610
<Box sx={{ width: '100%', mb: 4 }}>
356-
<Typography variant="h2" sx={{ mb: 2, color: 'primary.main' }}>
611+
<SectionHeader id="reporte-incidentes" variant="h2">
357612
{t("codeOfConduct.reporting.title")}
358-
</Typography>
613+
</SectionHeader>
359614
<Typography sx={{ textAlign: 'justify', mb: 2 }}>
360615
{t("codeOfConduct.reporting.description")}
361616
</Typography>
@@ -391,9 +646,9 @@ function CodeOfConduct() {
391646

392647
{/* Sección 8: Compromiso */}
393648
<Box sx={{ width: '100%', mb: 4 }}>
394-
<Typography variant="h2" sx={{ mb: 2, color: 'primary.main' }}>
649+
<SectionHeader id="compromiso" variant="h2">
395650
{t("codeOfConduct.commitment.title")}
396-
</Typography>
651+
</SectionHeader>
397652
<Typography sx={{ textAlign: 'justify', mb: 2 }}>
398653
{t("codeOfConduct.commitment.description")}
399654
</Typography>

0 commit comments

Comments
 (0)