Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"rules": {
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": ["global"]
}
]
}
}
168 changes: 168 additions & 0 deletions components/Activity/HeroCarousel.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
.heroCarousel {
--hero-carousel-offset: 4.125rem;

background: #050816;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
min-height: calc(100vh - var(--hero-carousel-offset) - 2.5rem);

@media (max-width: 991.98px) {
min-height: calc(100vh - var(--hero-carousel-offset) - 2rem);
}

@media (max-width: 767.98px) {
min-height: calc(100svh - var(--hero-carousel-offset) - 1.25rem);
}
}

.carousel,
.item,
.slideCard {
min-height: inherit;
}

.carousel {
:global(.carousel-inner) {
min-height: inherit;
}

:global(.carousel-item) {
min-height: inherit;
}

:global(.carousel-indicators) {
right: auto;
bottom: 2rem;
left: 1rem;
justify-content: flex-start;
margin: 0;
}

:global(.carousel-indicators button) {
opacity: 0.9;
margin: 0 0.45rem 0 0;
border: 0;
border-radius: 999px;
background-color: rgb(255 255 255 / 65%);
width: 2.25rem;
height: 0.32rem;
}

:global(.carousel-control-prev),
:global(.carousel-control-next) {
opacity: 0.95;
width: 6rem;
}

:global(.carousel-control-prev-icon),
:global(.carousel-control-next-icon) {
Comment thread
dethan3 marked this conversation as resolved.
filter: drop-shadow(0 12px 24px rgb(0 0 0 / 50%));
}

@media (max-width: 767.98px) {
:global(.carousel-indicators) {
right: 1rem;
bottom: 1rem;
left: 1rem;
justify-content: center;
}

:global(.carousel-indicators button) {
margin: 0 0.3rem;
width: 1.4rem;
height: 0.26rem;
}

:global(.carousel-control-prev),
:global(.carousel-control-next) {
display: none;
}
}
}

.slideCard {
background: linear-gradient(
90deg,
rgb(5 8 22 / 96%) 0%,
rgb(5 8 22 / 88%) 28%,
rgb(5 8 22 / 46%) 58%,
rgb(5 8 22 / 16%) 100%
);

@media (max-width: 767.98px) {
background: linear-gradient(
180deg,
rgb(5 8 22 / 18%) 0%,
rgb(5 8 22 / 54%) 44%,
rgb(5 8 22 / 92%) 100%
);
}
}

.mediaPane {
min-height: 19rem;

@media (max-width: 767.98px) {
min-height: 14rem;
}
}

.mediaPane::after {
position: absolute;
inset: 0;
background:
linear-gradient(180deg, rgb(5 8 22 / 18%) 0%, rgb(5 8 22 / 48%) 58%, rgb(5 8 22 / 82%) 100%),
linear-gradient(90deg, rgb(5 8 22 / 10%) 0%, rgb(5 8 22 / 24%) 32%, rgb(5 8 22 / 72%) 100%);
pointer-events: none;
content: '';

@media (max-width: 767.98px) {
background:
linear-gradient(180deg, rgb(5 8 22 / 16%) 0%, rgb(5 8 22 / 42%) 42%, rgb(5 8 22 / 88%) 100%),
linear-gradient(90deg, rgb(5 8 22 / 18%) 0%, rgb(5 8 22 / 16%) 100%);
}
}

.description {
max-width: 40rem;

:global(p) {
margin-bottom: 0;
font-size: clamp(1rem, 1.6vw, 1.28rem);
line-height: 1.55;
}

:global(button) {
display: none;
}

@media (max-width: 767.98px) {
max-width: none;
}
}
Comment thread
dethan3 marked this conversation as resolved.

.actionButton {
transition:
transform 180ms ease,
box-shadow 180ms ease,
background 180ms ease,
border-color 180ms ease;
box-shadow: 0 12px 28px rgb(14 165 233 / 18%);
border: 1px solid rgb(255 255 255 / 20%);
border-radius: 0.9rem;
background: linear-gradient(135deg, rgb(255 255 255 / 92%) 0%, rgb(224 242 254 / 96%) 100%);
color: #0f172a;
letter-spacing: 0.02em;

@media (max-width: 767.98px) {
border-radius: 0.8rem;
}
}

.actionButton:hover,
.actionButton:focus,
.actionButton:active {
transform: translateY(-2px);
box-shadow: 0 18px 40px rgb(14 165 233 / 22%);
border-color: rgb(255 255 255 / 34%);
background: linear-gradient(135deg, #ffffff 0%, #e0f2fe 100%);
color: #0f172a;
}
185 changes: 185 additions & 0 deletions components/Activity/HeroCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { CSSProperties, FC, useContext, useEffect, useState } from 'react';
import { TextTruncate } from 'idea-react';
import { TableCellLocation } from 'mobx-lark';
import { Badge, Button, Card, Carousel, Col, Container, Row, Stack } from 'react-bootstrap';

import { Activity, ActivityModel } from '../../models/Activity';
import { I18nContext } from '../../models/Translation';
import { LarkImage } from '../LarkImage';
import styles from './HeroCarousel.module.less';
Comment on lines +1 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

修复 ESLint 报错:导入排序不符合 simple-import-sort/imports 规则。

该插件设计为配合 autofix 使用(eslint --fix 或编辑器 ESLint 扩展),可一键修复。外部包按字母序 idea-react → mobx-lark → react → react-bootstrap 排列即可通过 lint 检查。

🔧 建议修改
-import { CSSProperties, FC, useContext, useEffect, useState } from 'react';
-import { TextTruncate } from 'idea-react';
-import { TableCellLocation } from 'mobx-lark';
-import { Badge, Button, Card, Carousel, Col, Container, Row, Stack } from 'react-bootstrap';
+import { TextTruncate } from 'idea-react';
+import { TableCellLocation } from 'mobx-lark';
+import { CSSProperties, FC, useContext, useEffect, useState } from 'react';
+import { Badge, Button, Card, Carousel, Col, Container, Row, Stack } from 'react-bootstrap';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { CSSProperties, FC, useContext, useEffect, useState } from 'react';
import { TextTruncate } from 'idea-react';
import { TableCellLocation } from 'mobx-lark';
import { Badge, Button, Card, Carousel, Col, Container, Row, Stack } from 'react-bootstrap';
import { Activity, ActivityModel } from '../../models/Activity';
import { I18nContext } from '../../models/Translation';
import { LarkImage } from '../LarkImage';
import styles from './HeroCarousel.module.less';
import { TextTruncate } from 'idea-react';
import { TableCellLocation } from 'mobx-lark';
import { CSSProperties, FC, useContext, useEffect, useState } from 'react';
import { Badge, Button, Card, Carousel, Col, Container, Row, Stack } from 'react-bootstrap';
import { Activity, ActivityModel } from '../../models/Activity';
import { I18nContext } from '../../models/Translation';
import { LarkImage } from '../LarkImage';
import styles from './HeroCarousel.module.less';
🧰 Tools
🪛 ESLint

[error] 1-9: Run autofix to sort these imports!

(simple-import-sort/imports)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Activity/HeroCarousel.tsx` around lines 1 - 9, Reorder the top
imports in HeroCarousel.tsx to satisfy simple-import-sort/imports: group and
alphabetize external package imports so they read idea-react (TextTruncate) →
mobx-lark (TableCellLocation) → react (CSSProperties, FC, useContext, useEffect,
useState) → react-bootstrap (Badge, Button, Card, Carousel, Col, Container, Row,
Stack); keep local imports (Activity, ActivityModel, I18nContext, LarkImage,
styles) after external packages and preserve existing named imports and spacing.


const timestampOf = (value: unknown) => {
if (typeof value === 'number') return value;
if (typeof value === 'string') {
const time = new Date(value).getTime();

return Number.isFinite(time) ? time : 0;
}

return 0;
};

const formatDateLabel = (value: unknown, locale: string) => {
const timestamp = timestampOf(value);

return !timestamp
? ''
: new Intl.DateTimeFormat(locale, {
month: 'short',
day: 'numeric',
}).format(timestamp);
};

const locationTextOf = ({ city, location }: Activity) =>
[(city as string) || '', (location as TableCellLocation | undefined)?.full_address || '']
.filter(Boolean)
.join(' · ');

const descriptionOf = (activity: Activity) =>
(activity.summary as string) || locationTextOf(activity) || (activity.type as string) || '';

export const HeroCarousel: FC<{ activities: Activity[] }> = ({ activities }) => {
const { currentLanguage, t } = useContext(I18nContext);
const [heroStyle, setHeroStyle] = useState<CSSProperties>();
const [descriptionRows, setDescriptionRows] = useState(3);
const infoBodyStyle = { minHeight: 'clamp(0rem, 38vh, 24rem)' } as CSSProperties;

useEffect(() => {
const navbar = document.querySelector<HTMLElement>('nav');
const syncHeroOffset = () => {
const navbarHeight = navbar?.getBoundingClientRect().height || 56;

setHeroStyle({
'--hero-carousel-offset': `${navbarHeight}px`,
} as CSSProperties);
};
const observer = navbar && new ResizeObserver(syncHeroOffset);

syncHeroOffset();
if (navbar) observer?.observe(navbar);

return () => observer?.disconnect();
}, []);

useEffect(() => {
const syncDescriptionRows = () => setDescriptionRows(window.innerWidth <= 767.98 ? 4 : 3);
syncDescriptionRows();

const observer = new ResizeObserver(syncDescriptionRows);
observer.observe(document.body);

return () => observer.disconnect();
}, []);

return (
<Container
as="section"
fluid
className={`${styles.heroCarousel} position-relative`}
style={heroStyle}
>
<Carousel
fade
touch
pause="hover"
interval={6500}
indicators={activities.length > 1}
controls={activities.length > 1}
className={`${styles.carousel} h-100`}
>
{activities.map(activity => {
const { id, type, name, host, startTime, cardImage } = activity;

const href = ActivityModel.getLink(activity);
const hosts = ((host as string[]) || []).slice(0, 2);
const locationText = locationTextOf(activity);
const dateText = formatDateLabel(startTime, currentLanguage);
const title = (name as string) || t('activity');
const description = descriptionOf(activity);
const image = cardImage || activity.image;

return (
<Carousel.Item key={id as string} className={`${styles.item} h-100`}>
<Card
className={`${styles.slideCard} h-100 rounded-0 border-0 bg-transparent text-white`}
>
<Row className="g-0 h-100 flex-column-reverse flex-md-row">
<Col
xs={12}
md={6}
lg={5}
className="d-flex align-items-center position-relative z-1"
>
<Container fluid="md" className="px-3 px-md-4 px-xl-5 py-5">
<Card.Body
className="p-0 d-flex flex-column justify-content-center"
style={infoBodyStyle}
>
<Stack direction="horizontal" gap={2} className="flex-wrap mb-3 mb-md-4">
{(hosts.length ? hosts : [(type as string) || t('hackathon')]).map(
item => (
<Badge
key={item}
pill
bg="info"
text="dark"
className="px-3 py-2 fw-semibold"
>
{item}
</Badge>
),
)}
{(dateText || t('event_duration')) && (
<Badge pill bg="light" text="dark" className="px-3 py-2 fw-semibold">
{dateText || t('event_duration')}
</Badge>
)}
</Stack>

<Card.Title
as="h1"
className="display-3 fw-bold lh-1 mb-3 mb-md-4"
style={{ textWrap: 'balance' }}
>
{title}
</Card.Title>

<TextTruncate
rows={descriptionRows}
className={`${styles.description} text-white-50 mb-4`}
>
{description}
</TextTruncate>

<Stack
direction="horizontal"
gap={3}
className="flex-wrap align-items-start align-items-md-center"
>
<Card.Text className="mb-0 fs-6 text-info-emphasis fw-semibold">
{locationText || t('open_source_bazaar')}
</Card.Text>
<Button
href={href}
variant="light"
className={`${styles.actionButton} px-4 py-2 fw-semibold text-uppercase`}
>
{t('hackathon_register_now')}
</Button>
</Stack>
</Card.Body>
</Container>
</Col>

<Col xs={12} md={6} lg={7} className={`${styles.mediaPane} position-relative`}>
<LarkImage src={image} alt={title} className="w-100 h-100 object-fit-cover" />
</Col>
</Row>
</Card>
</Carousel.Item>
);
})}
</Carousel>
</Container>
);
};
4 changes: 2 additions & 2 deletions components/Navigator/MainNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const MainNavigator: FC<MainNavigatorProps> = observer(({ menu }) => {
return (
<Navbar bg="dark" variant="dark" fixed="top" expand="lg">
<Container>
<Navbar.Brand href="/" className="fw-bolder d-flex align-items-center gap-2">
<Navbar.Brand href="/" className="fw-bolder d-flex align-items-center gap-2 text-nowrap">
<Image width={40} src={DefaultImage} alt={t('open_source_bazaar')} />
{t('open_source_bazaar')}
</Navbar.Brand>
Expand All @@ -117,7 +117,7 @@ export const MainNavigator: FC<MainNavigatorProps> = observer(({ menu }) => {
<Nav.Link
key={`${href}-${title}`}
href={href}
className={pathname === `${href}` ? 'fw-bolder text-light' : ''}
className={`text-nowrap ${pathname === `${href}` ? 'fw-bolder text-light' : ''}`}
>
{title}
</Nav.Link>
Expand Down
Loading
Loading