-
Notifications
You must be signed in to change notification settings - Fork 9
[add] Hero Carousel component with Aurora Gradient style in Home page #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3b66c69
ed2cf15
51d09ee
f527d0e
e164187
a5e8e6c
bc438a9
9e3a245
f023eef
e05aaed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "rules": { | ||
| "selector-pseudo-class-no-unknown": [ | ||
| true, | ||
| { | ||
| "ignorePseudoClasses": ["global"] | ||
| } | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| .heroCarousel { | ||
| --hero-carousel-offset: 4.125rem; | ||
|
|
||
| background: #050816; | ||
| 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) { | ||
|
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; | ||
| } | ||
| } | ||
|
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; | ||
| } | ||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 修复 ESLint 报错:导入排序不符合 该插件设计为配合 autofix 使用( 🔧 建议修改-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
Suggested change
🧰 Tools🪛 ESLint[error] 1-9: Run autofix to sort these imports! (simple-import-sort/imports) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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> | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.