@hellobetterdigitalnz/selwynui 0.0.1-48 → 0.0.1-49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/Components/DataDisplay/KPIBlock/KPIBlockProps.d.ts +2 -0
  2. package/dist/Components/DataDisplay/ListingDetailBlock/ListingDetailBlock.d.ts +1 -1
  3. package/dist/Components/DataDisplay/ListingDetailBlock/ListingDetailBlockProps.d.ts +15 -9
  4. package/dist/Components/DataDisplay/Media/Media.d.ts +3 -0
  5. package/dist/Components/DataDisplay/Media/MediaProps.d.ts +6 -0
  6. package/dist/index.cjs.js +12 -12
  7. package/dist/index.cjs.js.map +1 -1
  8. package/dist/index.es.js +1983 -1991
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/selwynui.css +1 -1
  11. package/package.json +2 -1
  12. package/src/Components/DataDisplay/DetailsCard/DetailsCard.tsx +1 -4
  13. package/src/Components/DataDisplay/KPIBlock/KPIBlock.tsx +12 -2
  14. package/src/Components/DataDisplay/KPIBlock/KPIBlockProps.tsx +3 -1
  15. package/src/Components/DataDisplay/KPIBlock/kpiBlock.module.scss +69 -0
  16. package/src/Components/DataDisplay/ListBlock/ListBlock.stories.tsx +3 -3
  17. package/src/Components/DataDisplay/ListBlock/ListBlock.tsx +20 -8
  18. package/src/Components/DataDisplay/ListingDetailBlock/ListingDetailBlock.stories.tsx +55 -9
  19. package/src/Components/DataDisplay/ListingDetailBlock/ListingDetailBlock.tsx +55 -82
  20. package/src/Components/DataDisplay/ListingDetailBlock/ListingDetailBlockProps.tsx +18 -12
  21. package/src/Components/DataDisplay/ListingDetailBlock/listingDetailBlock.module.scss +21 -13
  22. package/src/Components/DataDisplay/ListingDetailBlock/listingDetailBlock.module.scss~ +248 -0
  23. package/src/Components/DataDisplay/Media/Media.stories.tsx +42 -0
  24. package/src/Components/DataDisplay/Media/Media.tsx +57 -0
  25. package/src/Components/DataDisplay/Media/MediaProps.tsx +7 -0
  26. package/src/Components/DataDisplay/Media/media.scss +14 -0
  27. package/src/Components/DataDisplay/PathwayBlock/PathwayBlock.stories.tsx +24 -14
  28. package/src/Components/DataDisplay/PathwayBlock/PathwayBlock.stories.tsx~ +80 -0
  29. package/src/Components/DataDisplay/PathwayBlock/pathwayBlock.module.scss +2 -0
@@ -11,17 +11,14 @@ const DetailsCard = (props: DetailsCardProps) => {
11
11
  image = '',
12
12
  variation= 'long',
13
13
  link = {},
14
- pillar = 'visit'
15
14
  } = props;
16
15
 
17
16
  const classes = [
18
17
  styles.detailsCard,
19
- `${pillar}--light`,
20
- "visit",
21
18
  "detailsCard",
22
19
  ];
23
20
 
24
- if(variation === 'short') {
21
+ if(variation==='short') {
25
22
  classes.push(styles.detailsCardShort)
26
23
  }
27
24
 
@@ -3,6 +3,7 @@ import styles from './kpiBlock.module.scss';
3
3
  import Container from "../../Shared/Container/Container.tsx";
4
4
  import PillarIcon from "../../Shared/PillarIcon/PillarIcon.tsx";
5
5
  import ElementHolder from "../../Shared/ElementHolder/ElementHolder.tsx";
6
+ import cslx from "clsx";
6
7
 
7
8
 
8
9
  const KPIBlock = (props: KPIBlockProps) => {
@@ -15,9 +16,18 @@ const KPIBlock = (props: KPIBlockProps) => {
15
16
  paddingBottom,
16
17
  level,
17
18
  marginBottom,
18
- marginTop
19
+ marginTop,
20
+ spacingBottom='xsm',
19
21
  } = props;
20
22
 
23
+ const className = cslx(
24
+ styles.kpi ,
25
+ `${pillar}`,
26
+ {
27
+ [styles[`kpi--pb-${spacingBottom}`]]: spacingBottom,
28
+ },
29
+ );
30
+
21
31
  return (
22
32
  <ElementHolder
23
33
  paddingTop={paddingTop}
@@ -27,7 +37,7 @@ const KPIBlock = (props: KPIBlockProps) => {
27
37
  marginBottom={marginBottom}
28
38
  marginTop={marginTop}
29
39
  >
30
- <div className={`${styles.kpi}`}>
40
+ <div className={`${styles.kpi} ${className}`}>
31
41
  <Container>
32
42
  <div className={styles.kpiWrapper}>
33
43
  <div className={styles.header}>
@@ -9,7 +9,9 @@ interface KPIBlockProps extends ElementHolderProps{
9
9
  title?: string;
10
10
  description?: string;
11
11
  stats?: KPIItem[];
12
- pillar: 'main' | 'visit' | 'live' | 'business' | 'participate' | 'taste'
12
+ pillar: 'main' | 'visit' | 'live' | 'business' | 'participate' | 'taste';
13
+ spacingTop?: "none" | "xsm" |"sm" | "md" | "lg" ;
14
+ spacingBottom?: "none" | "xsm" |"sm" | "md" | "lg" | "xl";
13
15
  }
14
16
 
15
17
  export default KPIBlockProps;
@@ -1,4 +1,73 @@
1
1
  .kpi {
2
+
3
+ &Inner {
4
+ //position: relative;
5
+ z-index: 1;
6
+ }
7
+
8
+
9
+ &--pb-none {
10
+ .pillarIcon {
11
+ bottom:0;
12
+ }
13
+ }
14
+
15
+ &--pb-xsm {
16
+ .pillarIcon {
17
+ bottom: -72px;
18
+ }
19
+ }
20
+
21
+ &--pb-sm {
22
+ .pillarIcon {
23
+ bottom: -90px;
24
+ }
25
+ }
26
+
27
+ &--pb-md {
28
+ .pillarIcon {
29
+ bottom: -120px;
30
+ }
31
+ }
32
+
33
+ &--pb-lg {
34
+ padding-bottom: 25px;
35
+ .pillarIcon {
36
+ bottom: -144px;
37
+ }
38
+ }
39
+
40
+ &--pt-none {
41
+ .pillarIcon {
42
+ top:0;
43
+ }
44
+ }
45
+
46
+ &--pt-xsm {
47
+ .pillarIcon {
48
+ top: -72px;
49
+ }
50
+ }
51
+
52
+ &--pt-sm {
53
+ .pillarIcon {
54
+ top: -90px;
55
+ }
56
+ }
57
+
58
+ &--pt-md {
59
+ .pillarIcon {
60
+ top: -120px;
61
+ }
62
+ }
63
+
64
+ &--pt-lg {
65
+ padding-top: 25px;
66
+ .pillarIcon {
67
+ top: -144px;
68
+ }
69
+ }
70
+
2
71
  .kpiWrapper {
3
72
  display: flex;
4
73
  flex-direction: inherit;
@@ -46,9 +46,9 @@ const NoFilter: Story = {
46
46
  action={<Button label={'ALL VENUE'} secondaryIcon={<ArrowRight/>} level={"secondary"}/>}
47
47
  cards={
48
48
  [
49
- { title: "Ballooning Canterbury", link: "#", image: "./img/card-one.png", category: "adventure", pillar: "live" },
50
- { title: "Ski Resort", link: "#", image: "./img/card-two.png", category: "ski" },
51
- { title: "Nature Walk", link: "#", image: "./img/card-three.png", category: "nature" },
49
+ { title: "Ballooning Canterbury", link: "#", image: "./img/card-one.png",pillar: "live" },
50
+ { title: "Ski Resort", link: "#", image: "./img/card-two.png"},
51
+ { title: "Nature Walk", link: "#", image: "./img/card-three.png" },
52
52
  { title: "City Tour", link: "#", image: "./img/card-four.png" }
53
53
  ]
54
54
  }
@@ -81,7 +81,6 @@ const ListBlock = (props: ListBlockProps) => {
81
81
  </div>
82
82
  )}
83
83
 
84
- {/* Mobile Filters */}
85
84
  {dynamicFilters.length > 0 && (
86
85
  <div className={`${styles.pathwayFilters} ${styles.pathwayFilterMobile}`}>
87
86
  <select
@@ -105,14 +104,27 @@ const ListBlock = (props: ListBlockProps) => {
105
104
 
106
105
  {/* Cards */}
107
106
  <div className={styles.pathwayCards}>
108
- <div className={styles.pathwayCardsWrap}>
109
- {cards.filter(c => filterCards(c.category)).map((card, idx) => (
110
- <div key={idx} className={styles.pathwayCard}>
111
- <DetailsCard {...card} variation="short" />
112
- </div>
113
- ))}
114
- </div>
107
+ {activeFilter ? (
108
+ <div className={styles.pathwayCardsWrap}>
109
+ {cards
110
+ .filter(c => filterCards(c.category))
111
+ .map((card, idx) => (
112
+ <div key={idx} className={styles.pathwayCard}>
113
+ <DetailsCard {...card} variation="short" />
114
+ </div>
115
+ ))}
116
+ </div>
117
+ ) : (
118
+ <div className={styles.pathwayCardsWrap}>
119
+ {cards.map((card, idx) => (
120
+ <div key={idx} className={styles.pathwayCard}>
121
+ <DetailsCard {...card} variation="short" />
122
+ </div>
123
+ ))}
124
+ </div>
125
+ )}
115
126
  </div>
127
+
116
128
  </div>
117
129
  </Container>
118
130
  </div>
@@ -3,6 +3,9 @@ import React from "react"
3
3
  import { Meta, StoryObj } from "@storybook/react-vite";
4
4
  import ListingDetailBlock from "./ListingDetailBlock";
5
5
  import ElementHolder from "../../Shared/ElementHolder/ElementHolder";
6
+ import {Clock, DeviceMobile, MapPin, Mountains, Path} from "../../Icons";
7
+ import ArrowRight from "../../Icons/Arrows/ArrowRight/ArrowRight.tsx";
8
+ import {Button} from "../../Form";
6
9
 
7
10
  const meta: Meta = {
8
11
  title: "Layout / ListingDetailBlock",
@@ -21,8 +24,16 @@ export const Default: Story = {
21
24
  render: () => (
22
25
  <ElementHolder paddingTop="sm" paddingBottom="sm" pillar="visit" level="light">
23
26
  <ListingDetailBlock
24
- title="For beginners and kids the dedicated learner slope has gentle runs, easy sunkid carpet and platter lifts."
25
- description={`Those more advanced can enjoy the long cruising runs by taking the Easy Rider chairlift and three T-bars to the top. For the more adventurous there's challenging terrains and a freestyle park. A fleet of rental equipment is available for hire and a range of professional lessons available.\n\nThose more advanced can enjoy the long cruising runs by taking the Easy Rider chairlift and three T-bars to the top. For the more adventurous there’s challenging terrains and a freestyle park. A fleet of rental equipment is available for hire and a range of professional lessons available.\n\nPorters Lodge at the base of the mountain provides lodging as well as a fantastic restaurant and bar. Enjoy a dedicated snow play area adjacent to the lodge for tobogganing, the perfect setting for a family adventure.`}
27
+ title={
28
+ <>
29
+ <h4>For beginners and kids the dedicated learner slope has gentle runs, easy sunkid carpet and platter lifts.</h4>
30
+ <br/>
31
+ <p>Those more advanced can enjoy the long cruising runs by taking the Easy Rider chairlift and three T-bars to the top. For the more adventurous there's challenging terrains and a freestyle park. A fleet of rental equipment is available for hire and a range of professional lessons available.</p>
32
+ <br/>
33
+ <p>Porters Lodge at the base of the mountain provides lodging as well as a fantastic restaurant and bar. Enjoy a dedicated snow play area adjacent to the lodge for tobogganing, the perfect setting for a family adventure.</p>
34
+ <br/>
35
+ </>
36
+ }
26
37
  facilities={[
27
38
  "Hot food",
28
39
  "Ski school",
@@ -34,13 +45,48 @@ export const Default: Story = {
34
45
  "Shuttle bus",
35
46
  "Gear rental",
36
47
  ]}
37
- distance="91 km, approximately 1.25 hours"
38
- elevation="Top 1980m / Base 1302m"
39
- address="State Highway 73\nSelwyn, New Zealand"
40
- hours="9am–4pm, daily during winter"
41
- phone="03 318 4731"
42
- websiteUrl="https://porters.nz"
43
- websiteText="WEBSITE"
48
+ callToActions={[
49
+ {
50
+ icon: <Path type={'fill'}/>,
51
+ label: 'Distance from Christchurch Airport',
52
+ value: '91 km, approximately 1.25 hours',
53
+ },
54
+ {
55
+ icon: <Mountains type={'fill'}/>,
56
+ label: 'Elevations',
57
+ value: 'Top 1980m / Base 1302m',
58
+ },
59
+ {
60
+ icon: <MapPin type={'fill'}/>,
61
+ label: 'Address',
62
+ value: 'State Highway 73, Selwyn, New Zealand',
63
+ },
64
+ {
65
+ icon: <Clock type={'fill'}/>,
66
+ label: 'Hours',
67
+ value: '9am–4pm, daily during winter',
68
+ },
69
+ {
70
+ icon: <DeviceMobile type={'fill'}/>,
71
+ label: 'Phone',
72
+ value: '03 318 4731',
73
+ url: 'tel:033184731',
74
+ target: '_blank',
75
+ valueClassName: 'link',
76
+ },
77
+ ]}
78
+ callToActionButton={{
79
+ button: (
80
+ <Button
81
+ label="WEBSITE"
82
+ type="button"
83
+ size="default"
84
+ level="primary"
85
+ style="solid"
86
+ secondaryIcon={<ArrowRight/>}
87
+ />
88
+ )
89
+ }}
44
90
  />
45
91
  </ElementHolder>
46
92
  ),
@@ -1,99 +1,72 @@
1
- import ListingDetailBlockProps from "./ListingDetailBlockProps";
1
+ import ListingDetailBlockProps from "./ListingDetailBlockProps";
2
2
  import styles from "./ListingDetailBlock.module.scss";
3
- import {CheckCircle, Clock, DeviceMobile, MapPin, Mountains, Path} from "../../Icons";
4
- import {Button} from "../../Form";
5
- import ArrowRight from "../../Icons/Arrows/ArrowRight/ArrowRight.tsx";
3
+ import {CheckCircle} from "../../Icons";
6
4
  import Container from "../../Shared/Container/Container.tsx";
5
+ import { ReactNode } from "react";
7
6
 
8
7
  const ListingDetailBlock = ({
9
- title,
10
- description,
11
- facilities,
12
- distance,
13
- elevation,
14
- address,
15
- hours,
16
- phone,
17
- websiteUrl,
18
- }: ListingDetailBlockProps) => {
8
+ title,
9
+ facilities,
10
+ callToActions = [],
11
+ callToActionButton,
12
+ }: ListingDetailBlockProps) => {
13
+
14
+ const renderIcon = (icon: ReactNode | string): ReactNode => {
15
+ if (typeof icon === 'string') {
16
+ return <span dangerouslySetInnerHTML={{ __html: icon }} />;
17
+ }
18
+ return icon;
19
+ };
20
+
19
21
  return (
20
22
  <Container>
21
- <div className={styles.listingDetail}>
22
- {/* Main Content - Left */}
23
- <div className={styles.mainContent}>
24
- <h2 className={styles.title}>{title}</h2>
25
- <p className={styles.description}>{description}</p>
23
+ <div className={styles.listingDetail}>
26
24
 
27
- <h3 className={styles.sectionTitle}>Facilities</h3>
28
- <ul className={styles.facilitiesList}>
29
- {facilities.map((facility, index) => (
30
- <li key={index} className={styles.facilityItem}>
31
- <CheckCircle type={'fill'}/> {facility}
32
- </li>
33
- ))}
34
- </ul>
35
- </div>
25
+ <div className={styles.mainContent}>
26
+ <div className={`typography`}>{title}</div>
36
27
 
37
- {/* Sidebar - Right */}
38
- <div className={styles.sidebar}>
39
- {distance && (
40
- <div className={styles.infoItem}>
41
- <Path type={'fill'}/>
42
- <div className={styles.sidebarItems}>
43
- <div className={styles.infoLabel}>Distance from Christchurch Airport</div>
44
- <div className={styles.infoValue}>{distance}</div>
45
- </div>
46
- </div>
47
- )}
28
+ <h3 className={styles.sectionTitle}>Facilities</h3>
29
+ <ul className={styles.facilitiesList}>
30
+ {facilities.map((facility, index) => (
31
+ <li key={index} className={styles.facilityItem}>
32
+ <CheckCircle type={'fill'}/> {facility}
33
+ </li>
34
+ ))}
35
+ </ul>
36
+ </div>
48
37
 
49
- {elevation && (
50
- <div className={styles.infoItem}>
51
- <Mountains type={'fill'}/>
52
- <div>
53
- <div className={styles.infoLabel}>Elevations</div>
54
- <div className={styles.infoValue}>{elevation}</div>
55
- </div>
56
- </div>
57
- )}
58
38
 
59
- {address && (
60
- <div className={styles.infoItem}>
61
- <MapPin type={'fill'}/>
62
- <div>
63
- <div className={styles.infoLabel}>Address</div>
64
- <div className={styles.infoValue}>{address}</div>
39
+ <div className={styles.sidebar}>
40
+ {callToActions.map((item, index) => (
41
+ <div key={index} className={styles.infoItem}>
42
+ {renderIcon(item.icon)}
43
+ <div className={item.label === 'Distance from Christchurch Airport' ? styles.sidebarItems : undefined}>
44
+ <div className={styles.infoLabel}>{item.label}</div>
45
+ {item.url ? (
46
+ <a
47
+ href={item.url}
48
+ className={item.valueClassName === 'link' ? styles.infoValueLink : styles.infoValue}
49
+ target={item.target}
50
+ rel="noopener noreferrer"
51
+ >
52
+ {item.value}
53
+ </a>
54
+ ) : (
55
+ <div className={item.valueClassName === 'link' ? styles.infoValueLink : styles.infoValue}>
56
+ {item.value}
57
+ </div>
58
+ )}
59
+ </div>
65
60
  </div>
66
- </div>
67
- )}
68
-
69
- {hours && (
70
- <div className={styles.infoItem}>
71
- <Clock type={'fill'}/>
72
- <div>
73
- <div className={styles.infoLabel}>Hours</div>
74
- <div className={styles.infoValue}>{hours}</div>
75
- </div>
76
- </div>
77
- )}
61
+ ))}
78
62
 
79
- {phone && (
80
- <div className={styles.infoItem}>
81
- <DeviceMobile type={'fill'}/>
82
- <div>
83
- <div className={styles.infoLabel}>Phone</div>
84
- <div className={styles.infoValuePhone}>{phone}</div>
63
+ {callToActionButton && (
64
+ <div className={styles.button}>
65
+ {callToActionButton.button}
85
66
  </div>
86
- </div>
87
- )}
88
-
89
- {websiteUrl && (
90
-
91
- <div>
92
- <Button label={'WEBSITE'} type={'button'} size={'default'} level={"primary"} style={'solid'} secondaryIcon={<ArrowRight/>} />
93
- </div>
94
- )}
67
+ )}
68
+ </div>
95
69
  </div>
96
- </div>
97
70
  </Container>
98
71
  );
99
72
  };
@@ -1,17 +1,23 @@
1
- // ListingDetailBlockProps.ts
1
+ import { ReactNode } from 'react';
2
+
3
+ export interface CallToActionItem {
4
+ icon: ReactNode | string;
5
+ label: string;
6
+ value: string;
7
+ url?: string;
8
+ target?: "_blank" | "_self" | "_parent" | "_top" | undefined;
9
+ valueClassName?: 'link' | 'default';
10
+ }
11
+
12
+ export interface CallToActionButton {
13
+ button: ReactNode;
14
+ }
15
+
2
16
  interface ListingDetailBlockProps {
3
- title: string;
4
- description: string;
17
+ title: ReactNode;
5
18
  facilities: string[];
6
-
7
- // New structured contact/info fields to match the design
8
- distance?: string;
9
- elevation?: string;
10
- address?: string;
11
- hours?: string;
12
- phone?: string;
13
- websiteUrl?: string;
14
- websiteText?: string; // e.g., "WEBSITE →"
19
+ callToActions?: CallToActionItem[];
20
+ callToActionButton?: CallToActionButton;
15
21
  }
16
22
 
17
23
  export default ListingDetailBlockProps;
@@ -3,13 +3,12 @@
3
3
  gap: 48px;
4
4
  padding: 32px;
5
5
  border-radius: 8px;
6
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
7
6
  position: relative;
8
7
 
9
8
  &::before {
10
9
  content: '';
11
10
  position: absolute;
12
- left: 50%;
11
+ left: 51%;
13
12
  top: 32px;
14
13
  bottom: 32px;
15
14
  width: 1px;
@@ -32,7 +31,7 @@
32
31
 
33
32
  .mainContent {
34
33
  width: 50%;
35
- padding-right: 72px;
34
+ padding-right: 20px;
36
35
 
37
36
  @media (max-width: 1200px) {
38
37
  padding-left: 80px;
@@ -51,7 +50,7 @@
51
50
  font-weight: 400;
52
51
  line-height: 1;
53
52
  letter-spacing: -1.92px;
54
- margin: 0 0 72px 0;
53
+ margin: 0 0 50px 0;
55
54
 
56
55
  @media (max-width: 900px) {
57
56
  font-size: 36px;
@@ -68,7 +67,7 @@
68
67
  font-size: 24px;
69
68
  line-height: 1.3;
70
69
  font-weight: 400;
71
- margin-bottom: 72px;
70
+ letter-spacing: -0.48px;
72
71
 
73
72
  @media (max-width: 900px) {
74
73
  font-size: 18px;
@@ -84,7 +83,9 @@
84
83
  .sectionTitle {
85
84
  font-size: 24px;
86
85
  font-weight: 600;
87
- margin: 72px 0 32px 0;
86
+ margin: 62px 0 22px 0;
87
+ letter-spacing: -0.48px;
88
+ line-height: 1.3;
88
89
 
89
90
  @media (max-width: 900px) {
90
91
  font-size: 20px;
@@ -103,12 +104,12 @@
103
104
  padding: 0;
104
105
  margin: 0;
105
106
  display: grid;
106
- grid-template-columns: repeat(2, 1fr);
107
- gap: 12px 24px;
107
+ grid-template-columns: repeat(2, 2fr);
108
+ gap: 9px;
108
109
 
109
110
  @media (max-width: 600px) {
110
111
  grid-template-columns: 1fr;
111
- gap: 16px;
112
+ gap: 2px;
112
113
  }
113
114
  }
114
115
 
@@ -142,7 +143,7 @@
142
143
  display: flex;
143
144
  flex-direction: column;
144
145
  gap: 60px;
145
- padding-left: 72px;
146
+ padding-left: 64px;
146
147
 
147
148
  @media (max-width: 1200px) {
148
149
  padding-left: 40px;
@@ -164,14 +165,14 @@
164
165
  .infoItem {
165
166
  display: flex;
166
167
  gap: 16px;
167
- align-items: flex-start;
168
+ align-items: center;
168
169
 
169
170
  @media (max-width: 900px) {
170
171
  gap: 12px;
171
172
  }
172
173
 
173
174
  svg {
174
- color: var(--color-taste);
175
+ color: var(--color-text);
175
176
  width: 48px;
176
177
  height: 48px;
177
178
  flex-shrink: 0;
@@ -223,10 +224,12 @@
223
224
  }
224
225
  }
225
226
 
226
- .infoValuePhone {
227
+ .infoValueLink {
228
+ color: var(--color-bg);
227
229
  font-size: 24px;
228
230
  text-decoration: underline;
229
231
  line-height: 1.3;
232
+ letter-spacing: -0.48px;
230
233
  font-weight: 400;
231
234
  margin-top: 0;
232
235
 
@@ -237,4 +240,9 @@
237
240
  @media (max-width: 480px) {
238
241
  font-size: 16px;
239
242
  }
243
+ }
244
+
245
+ .button {
246
+ margin-top: 5px;
247
+ max-width: 177px !important;
240
248
  }