|
| 1 | +import { Button, CarouselSlider, Image, makeStyles, tokens, typographyStyles } from '@fluentui/react-components'; |
| 2 | +import { |
| 3 | + Carousel, |
| 4 | + CarouselAnnouncerFunction, |
| 5 | + CarouselCard, |
| 6 | + CarouselNav, |
| 7 | + CarouselNavButton, |
| 8 | + CarouselNavContainer, |
| 9 | + CarouselViewport, |
| 10 | + Text, |
| 11 | +} from '@fluentui/react-components'; |
| 12 | +import * as React from 'react'; |
| 13 | + |
| 14 | +const useClasses = makeStyles({ |
| 15 | + bannerCard: { |
| 16 | + alignContent: 'center', |
| 17 | + borderRadius: tokens.borderRadiusXLarge, |
| 18 | + boxShadow: tokens.shadow16, |
| 19 | + height: '450px', |
| 20 | + textAlign: 'left', |
| 21 | + position: 'relative', |
| 22 | + }, |
| 23 | + image: { |
| 24 | + borderRadius: 'inherit', |
| 25 | + }, |
| 26 | + cardContainer: { |
| 27 | + display: 'flex', |
| 28 | + flexDirection: 'column', |
| 29 | + gap: tokens.spacingHorizontalS, |
| 30 | + |
| 31 | + position: 'absolute', |
| 32 | + left: '10%', |
| 33 | + top: '25%', |
| 34 | + borderRadius: tokens.borderRadiusLarge, |
| 35 | + boxShadow: tokens.shadow8, |
| 36 | + background: tokens.colorNeutralBackground1, |
| 37 | + padding: `${tokens.spacingHorizontalXXL} ${tokens.spacingVerticalXXXL}`, |
| 38 | + maxWidth: '270px', |
| 39 | + width: '50%', |
| 40 | + }, |
| 41 | + title: { |
| 42 | + ...typographyStyles.title3, |
| 43 | + }, |
| 44 | + subtext: { |
| 45 | + marginBottom: tokens.spacingVerticalM, |
| 46 | + ...typographyStyles.body1, |
| 47 | + }, |
| 48 | + container: { |
| 49 | + display: 'grid', |
| 50 | + gridTemplateColumns: '1fr', |
| 51 | + gridTemplateRows: 'auto 1fr', |
| 52 | + }, |
| 53 | + card: { |
| 54 | + minHeight: '100px', |
| 55 | + }, |
| 56 | + carousel: { |
| 57 | + flex: 1, |
| 58 | + paddingBottom: tokens.spacingVerticalXL, |
| 59 | + }, |
| 60 | + controls: { |
| 61 | + display: 'flex', |
| 62 | + flexDirection: 'column', |
| 63 | + gap: tokens.spacingVerticalSNudge, |
| 64 | + |
| 65 | + padding: `${tokens.spacingHorizontalMNudge} ${tokens.spacingVerticalMNudge}`, |
| 66 | + }, |
| 67 | + field: { |
| 68 | + flex: 1, |
| 69 | + gridTemplateColumns: 'minmax(100px, max-content) 1fr', |
| 70 | + }, |
| 71 | + dropdown: { |
| 72 | + maxWidth: 'max-content', |
| 73 | + }, |
| 74 | + carouselHeader: { |
| 75 | + display: 'flex', |
| 76 | + justifyContent: 'space-between', |
| 77 | + alignItems: 'center', |
| 78 | + gap: tokens.spacingVerticalSNudge, |
| 79 | + marginBottom: tokens.spacingHorizontalL, |
| 80 | + }, |
| 81 | + carouselHeaderTitle: { |
| 82 | + flex: '1', |
| 83 | + margin: '0', |
| 84 | + fontSize: tokens.fontSizeBase600, |
| 85 | + fontWeight: tokens.fontWeightSemibold, |
| 86 | + }, |
| 87 | + carouselNavigation: { width: 'fit-content', alignSelf: 'center', margin: '0' }, |
| 88 | + slider: { |
| 89 | + gap: tokens.spacingVerticalXXL, |
| 90 | + padding: `0 ${tokens.spacingVerticalXXL}`, |
| 91 | + }, |
| 92 | +}); |
| 93 | + |
| 94 | +const IMAGES = [ |
| 95 | + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg', |
| 96 | + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg', |
| 97 | + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg', |
| 98 | + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg', |
| 99 | + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg', |
| 100 | + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg', |
| 101 | +]; |
| 102 | + |
| 103 | +const BannerCard: React.FC<{ children: React.ReactNode; imageSrc: string; index: number }> = props => { |
| 104 | + const { children, imageSrc, index } = props; |
| 105 | + const classes = useClasses(); |
| 106 | + |
| 107 | + return ( |
| 108 | + <CarouselCard autoSize className={classes.bannerCard} aria-label={`${index + 1} of ${IMAGES.length}`}> |
| 109 | + <Image fit="cover" src={imageSrc} role="presentation" className={classes.image} /> |
| 110 | + |
| 111 | + <div className={classes.cardContainer}> |
| 112 | + <div className={classes.title}>{children}</div> |
| 113 | + <div className={classes.subtext}> |
| 114 | + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore |
| 115 | + magna aliqua. Ut enim ad minim veniam. |
| 116 | + </div> |
| 117 | + <div> |
| 118 | + <Button appearance="primary">Call to action</Button> |
| 119 | + </div> |
| 120 | + </div> |
| 121 | + </CarouselCard> |
| 122 | + ); |
| 123 | +}; |
| 124 | + |
| 125 | +const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => { |
| 126 | + return `Carousel slide ${index + 1} of ${totalSlides}`; |
| 127 | +}; |
| 128 | + |
| 129 | +export const TopNavigation = (): React.ReactElement => { |
| 130 | + const classes = useClasses(); |
| 131 | + |
| 132 | + return ( |
| 133 | + <div className={classes.container}> |
| 134 | + <div className={classes.card}> |
| 135 | + <Carousel circular draggable announcement={getAnnouncement} className={classes.carousel}> |
| 136 | + <div className={classes.carouselHeader}> |
| 137 | + <Text as="h1" className={classes.carouselHeaderTitle}> |
| 138 | + Carousel Title |
| 139 | + </Text> |
| 140 | + <CarouselNavContainer |
| 141 | + next={{ 'aria-label': 'go to next' }} |
| 142 | + prev={{ 'aria-label': 'go to prev' }} |
| 143 | + className={classes.carouselNavigation} |
| 144 | + > |
| 145 | + <CarouselNav>{index => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`} />}</CarouselNav> |
| 146 | + </CarouselNavContainer> |
| 147 | + </div> |
| 148 | + <CarouselViewport> |
| 149 | + <CarouselSlider className={classes.slider}> |
| 150 | + {IMAGES.map((imageSrc, index) => ( |
| 151 | + <BannerCard key={`image-${index}`} imageSrc={imageSrc} index={index}> |
| 152 | + Card {index + 1} |
| 153 | + </BannerCard> |
| 154 | + ))} |
| 155 | + </CarouselSlider> |
| 156 | + </CarouselViewport> |
| 157 | + </Carousel> |
| 158 | + </div> |
| 159 | + </div> |
| 160 | + ); |
| 161 | +}; |
| 162 | + |
| 163 | +TopNavigation.parameters = { |
| 164 | + docs: { |
| 165 | + description: { |
| 166 | + story: |
| 167 | + 'Top navigation places carousel controls at the header so users can see the title, page position, and navigation in one line. This story shows the default variant with previous and next buttons and dot pagination using CarouselNav inside CarouselNavContainer.', |
| 168 | + }, |
| 169 | + }, |
| 170 | +}; |
0 commit comments