@breathe-ds/ui 0.2.0 → 0.4.0

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 (49) hide show
  1. package/README.md +118 -13
  2. package/components/Accordion.tsx +6 -4
  3. package/components/AnchorNav.tsx +12 -4
  4. package/components/Avatar.tsx +37 -0
  5. package/components/BottomNav.tsx +1 -1
  6. package/components/Breadcrumb.tsx +45 -0
  7. package/components/BreatheMark.tsx +0 -13
  8. package/components/Button.tsx +11 -4
  9. package/components/Byline.tsx +105 -0
  10. package/components/CaptionBand.tsx +74 -0
  11. package/components/CareSteps.tsx +86 -0
  12. package/components/ChatPreview.tsx +138 -0
  13. package/components/CtaCard.tsx +52 -0
  14. package/components/FactStrip.tsx +55 -0
  15. package/components/FeaturedPosts.tsx +80 -0
  16. package/components/Footer.tsx +50 -30
  17. package/components/Header.tsx +4 -2
  18. package/components/Heading.tsx +86 -0
  19. package/components/InfoCard.tsx +116 -0
  20. package/components/LanguageSwitcher.tsx +1 -1
  21. package/components/List.tsx +81 -0
  22. package/components/Lockup.tsx +0 -18
  23. package/components/MapCard.tsx +158 -0
  24. package/components/MediaCard.tsx +266 -0
  25. package/components/PageVideoHero.tsx +65 -23
  26. package/components/PauseButton.tsx +41 -0
  27. package/components/PersonChip.tsx +49 -0
  28. package/components/PhotoSequence.tsx +99 -0
  29. package/components/Pill.tsx +42 -0
  30. package/components/PillList.tsx +20 -47
  31. package/components/PlanTimeline.tsx +66 -0
  32. package/components/PostArticle.tsx +102 -0
  33. package/components/PostCard.tsx +69 -0
  34. package/components/ProfessionalCard.tsx +446 -0
  35. package/components/ProfessionalModal.tsx +243 -0
  36. package/components/Rail.tsx +94 -0
  37. package/components/Rows.tsx +137 -0
  38. package/components/StoryPanel.tsx +107 -0
  39. package/components/TestimonialCard.tsx +138 -0
  40. package/components/Text.tsx +31 -0
  41. package/components/Timeline.tsx +69 -0
  42. package/components/VideoInviteCard.tsx +168 -0
  43. package/components/VideoStoryCard.tsx +382 -0
  44. package/components/useOnAppear.ts +39 -0
  45. package/package.json +34 -2
  46. package/theme.css +206 -0
  47. package/tokens/creeta.css +7 -2
  48. package/tokens/neutral.css +12 -4
  49. package/tokens/ttcab.css +14 -5
@@ -0,0 +1,116 @@
1
+ import type { ComponentType, ElementType, ReactNode } from "react";
2
+
3
+ /** lucide-style icon component (the card sets the size). */
4
+ type IconType = ComponentType<{ size?: number; strokeWidth?: number; className?: string; "aria-hidden"?: boolean | "true" }>;
5
+
6
+ export type InfoCardLink = {
7
+ href: string;
8
+ label: ReactNode;
9
+ /** Opens in a new tab (always a plain <a>) with an sr-only note after the label. */
10
+ external?: boolean;
11
+ };
12
+
13
+ type Props = {
14
+ /** Icon in the tinted bubble above the title (lucide, 20px, stroke 1.75). */
15
+ Icon: IconType;
16
+ title: ReactNode;
17
+ /** h2 on a page section (default), h3 inside a section that already has an h2. */
18
+ headingLevel?: "h2" | "h3";
19
+ /** Address lines: one <address> block, lines separated by <br>. */
20
+ address?: ReactNode[];
21
+ /** Body: one paragraph per item. */
22
+ lines?: ReactNode[];
23
+ /** Smaller closing line (e.g. "Outside these hours, by appointment"). */
24
+ note?: ReactNode;
25
+ /** Custom body, rendered after `address`/`lines`/`note`. */
26
+ children?: ReactNode;
27
+ /** Action links at the bottom ("Directions ↗ · Waze ↗"): underlined semibold,
28
+ * outbound arrow, separated by a middle dot. */
29
+ links?: InfoCardLink[];
30
+ /** Wrapper element. `section` by default; `li` inside a list of cards. */
31
+ as?: "section" | "div" | "li" | "article";
32
+ /** Link component of the app for page links (external links always use <a>). */
33
+ linkComponent?: ElementType;
34
+ /** sr-only after an external link. */
35
+ newTabLabel?: string;
36
+ className?: string;
37
+ };
38
+
39
+ const ArrowUpRight = () => (
40
+ <svg aria-hidden="true" viewBox="0 0 24 24" width={18} height={18} className="cg-arrow-go" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17 17 7M9 7h8v8" /></svg>
41
+ );
42
+
43
+ const body = "mt-2 leading-relaxed text-ink/80";
44
+ const link =
45
+ "mt-4 inline-flex min-h-11 items-center gap-1.5 font-semibold text-ink underline underline-offset-4 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink";
46
+
47
+ /**
48
+ * Info card of the Cuidado Global family — the three white cards of the
49
+ * novo.creeta clinic page ("Address", "Opening hours", "Where we see
50
+ * patients") and the contact page: a 44px tinted bubble with the icon, the
51
+ * title, the body (address lines, paragraphs, a smaller note) and, at the
52
+ * bottom, the action links with the outbound arrow, separated by a middle
53
+ * dot ("Directions ↗ · Waze ↗"). White on the page color (`surface`), 28px
54
+ * radius, 24px padding (32px from md).
55
+ */
56
+ export function InfoCard({
57
+ Icon,
58
+ title,
59
+ headingLevel: Heading = "h2",
60
+ address,
61
+ lines,
62
+ note,
63
+ children,
64
+ links,
65
+ as: Tag = "section",
66
+ linkComponent,
67
+ newTabLabel = "(opens in a new tab)",
68
+ className = "",
69
+ }: Props) {
70
+ const L: ElementType = linkComponent ?? "a";
71
+
72
+ return (
73
+ <Tag className={`rounded-2xl bg-card p-6 md:p-8 ${className}`}>
74
+ <span aria-hidden="true" className="flex h-11 w-11 items-center justify-center rounded-full bg-ink/[0.12] text-ink">
75
+ <Icon size={20} strokeWidth={1.75} />
76
+ </span>
77
+ <Heading className="mt-4 font-heading text-xl font-medium text-ink">{title}</Heading>
78
+
79
+ {address && address.length > 0 && (
80
+ <address className={`${body} not-italic`}>
81
+ {address.map((line, i) => (
82
+ <span key={i}>
83
+ {i > 0 && <br />}
84
+ {line}
85
+ </span>
86
+ ))}
87
+ </address>
88
+ )}
89
+ {lines?.map((line, i) => (
90
+ <p key={i} className={body}>
91
+ {line}
92
+ </p>
93
+ ))}
94
+ {note && <p className={`${body} text-sm`}>{note}</p>}
95
+ {children}
96
+
97
+ {links?.map((l, i) => (
98
+ <span key={`${i}-${l.href}`}>
99
+ {i > 0 && <span aria-hidden="true" className="mx-2 text-ink/80">·</span>}
100
+ {l.external ? (
101
+ <a href={l.href} target="_blank" rel="noopener noreferrer" className={link}>
102
+ {l.label}
103
+ <ArrowUpRight />
104
+ <span className="sr-only"> {newTabLabel}</span>
105
+ </a>
106
+ ) : (
107
+ <L href={l.href} className={link}>
108
+ {l.label}
109
+ <ArrowUpRight />
110
+ </L>
111
+ )}
112
+ </span>
113
+ ))}
114
+ </Tag>
115
+ );
116
+ }
@@ -114,7 +114,7 @@ export function LanguageSwitcher({ languages, mode = "menu", labels, className =
114
114
  </button>
115
115
  <ul
116
116
  id={id}
117
- className={`absolute right-0 top-full z-50 mt-2 min-w-[11rem] list-none origin-top-right rounded-[20px] bg-white p-1.5 shadow-[0_16px_40px_-16px_rgba(0,0,0,0.35)] transition duration-150 ease-out motion-reduce:transition-none ${
117
+ className={`absolute right-0 top-full z-50 mt-2 min-w-[11rem] list-none origin-top-right rounded-[20px] bg-card p-1.5 shadow-[0_16px_40px_-16px_rgba(0,0,0,0.35)] transition duration-150 ease-out motion-reduce:transition-none ${
118
118
  open ? "visible scale-100 opacity-100" : "invisible scale-95 opacity-0"
119
119
  }`}
120
120
  >
@@ -0,0 +1,81 @@
1
+ import type { ElementType, ReactNode } from "react";
2
+
3
+ export type ListItem = {
4
+ label: ReactNode;
5
+ /** With an href the item is a link: semibold, underlined, with an outbound arrow at the end. */
6
+ href?: string;
7
+ external?: boolean;
8
+ };
9
+
10
+ type Props = {
11
+ items: ListItem[];
12
+ /** `check` (default): bubble with a check — "what we assess", "what we treat".
13
+ * `arrow`: corner arrow (↳) — one sentence per item, as in the Footer notice. */
14
+ marker?: "check" | "arrow";
15
+ /** Link component of the app (e.g. next-intl Link). Default: <a>. */
16
+ linkComponent?: ElementType;
17
+ /** sr-only after an external link. */
18
+ newTabLabel?: string;
19
+ className?: string;
20
+ };
21
+
22
+ const Check = () => (
23
+ <svg aria-hidden="true" viewBox="0 0 24 24" width={14} height={14} fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
24
+ );
25
+ const CornerDownRight = () => (
26
+ <svg aria-hidden="true" viewBox="0 0 24 24" width={18} height={18} className="mt-[0.2rem] shrink-0" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 10 5 5-5 5" /><path d="M4 4v7a4 4 0 0 0 4 4h12" /></svg>
27
+ );
28
+ const ArrowUpRight = () => (
29
+ <svg aria-hidden="true" viewBox="0 0 24 24" width={16} height={16} className="cg-arrow-go ml-auto mt-1 shrink-0" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17 17 7M9 7h8v8" /></svg>
30
+ );
31
+
32
+ /**
33
+ * The family's list with a marker in front of each item (from novo.creeta:
34
+ * `Lista` on the method page, "O que tratamos aqui" on a treatment page, the
35
+ * notice in the footer).
36
+ * - check: a 24px bubble (ink at 12%) with the check; the marker is
37
+ * decoration, the <ul> is what says "list".
38
+ * - arrow: the corner arrow, for one sentence per item.
39
+ * - An item with `href` becomes a link: semibold, underline that darkens on
40
+ * hover, outbound arrow at the end stepping toward where it goes; 44px tap
41
+ * height without changing the rhythm (negative vertical margin).
42
+ */
43
+ export function List({ items, marker = "check", linkComponent, newTabLabel = "(opens in a new tab)", className = "" }: Props) {
44
+ const L: ElementType = linkComponent ?? "a";
45
+ const bubble = (
46
+ <span aria-hidden="true" className="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ink/[0.12] text-ink">
47
+ <Check />
48
+ </span>
49
+ );
50
+ const mark = marker === "check" ? bubble : <CornerDownRight />;
51
+ const gap = marker === "check" ? "gap-3" : "gap-2.5";
52
+
53
+ return (
54
+ <ul className={`list-none ${marker === "check" ? "space-y-3 text-ink" : "space-y-2 leading-relaxed text-ink/80"} ${className}`}>
55
+ {items.map((it, i) => (
56
+ <li key={`${i}-${it.href ?? ""}`}>
57
+ {it.href ? (
58
+ <L
59
+ href={it.href}
60
+ {...(it.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
61
+ className={`group/item -my-2 flex min-h-11 items-start ${gap} py-2.5 font-semibold text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink`}
62
+ >
63
+ {mark}
64
+ <span className="underline decoration-ink/40 underline-offset-4 transition-[text-decoration-color] duration-150 group-hover/item:decoration-ink motion-reduce:transition-none">
65
+ {it.label}
66
+ {it.external && <span className="sr-only"> {newTabLabel}</span>}
67
+ </span>
68
+ <ArrowUpRight />
69
+ </L>
70
+ ) : (
71
+ <span className={`flex items-start ${gap}`}>
72
+ {mark}
73
+ <span>{it.label}</span>
74
+ </span>
75
+ )}
76
+ </li>
77
+ ))}
78
+ </ul>
79
+ );
80
+ }
81
+
@@ -109,7 +109,6 @@ export function Lockup({
109
109
  if (variant === "wordmark") {
110
110
  return (
111
111
  <span className={`inline-flex flex-col${entranceCls} ${className}`} style={entranceStyle}>
112
- {entrance && <style>{ENTRANCE_CSS}</style>}
113
112
  {nameEl}
114
113
  {tagEl && <span className="mt-1">{tagEl}</span>}
115
114
  </span>
@@ -119,7 +118,6 @@ export function Lockup({
119
118
  if (variant === "stacked") {
120
119
  return (
121
120
  <span className={`inline-flex flex-col items-center text-center${entranceCls} ${className}`} style={entranceStyle}>
122
- {entrance && <style>{ENTRANCE_CSS}</style>}
123
121
  {mark}
124
122
  <span className="mt-4 flex flex-col items-center">
125
123
  {nameEl}
@@ -148,7 +146,6 @@ export function Lockup({
148
146
  if (symbolSide === "right") {
149
147
  return (
150
148
  <span className={`inline-flex flex-col items-start${entranceCls} ${className}`} style={entranceStyle}>
151
- {entrance && <style>{ENTRANCE_CSS}</style>}
152
149
  <span className={`inline-flex items-center ${GAP[size]}`}>
153
150
  {nameEl}
154
151
  {mark}
@@ -159,7 +156,6 @@ export function Lockup({
159
156
  }
160
157
  return (
161
158
  <span className={`inline-flex items-center ${GAP[size]}${entranceCls} ${className}`} style={entranceStyle}>
162
- {entrance && <style>{ENTRANCE_CSS}</style>}
163
159
  {mark}
164
160
  <span className="flex min-w-0 flex-col">
165
161
  {nameEl}
@@ -169,17 +165,3 @@ export function Lockup({
169
165
  );
170
166
  }
171
167
 
172
- /* Entrance: symbol (scale + slight rotation) → name (slides in from the left) → tagline.
173
- Same vocabulary as the Curtain; with reduced motion it becomes just a short fade. */
174
- const ENTRANCE_CSS = `
175
- .cg-lockup--entrance .cg-lockup__mark{ animation: cg-lockup-mark 500ms var(--ease-cg,cubic-bezier(.2,.8,.2,1)) var(--cg-entrance-delay,0ms) both; }
176
- .cg-lockup--entrance .cg-lockup__name{ animation: cg-lockup-name 420ms var(--ease-cg,cubic-bezier(.2,.8,.2,1)) calc(var(--cg-entrance-delay,0ms) + 420ms) both; }
177
- .cg-lockup--entrance .cg-lockup__tag{ animation: cg-lockup-tag 360ms ease calc(var(--cg-entrance-delay,0ms) + 700ms) both; }
178
- @keyframes cg-lockup-mark{ from{ opacity:0; transform:scale(.6) rotate(-16deg); } }
179
- @keyframes cg-lockup-name{ from{ opacity:0; transform:translateX(-8px); } }
180
- @keyframes cg-lockup-tag{ from{ opacity:0; transform:translateY(4px); } }
181
- @media (prefers-reduced-motion:reduce){
182
- .cg-lockup--entrance .cg-lockup__mark, .cg-lockup--entrance .cg-lockup__name, .cg-lockup--entrance .cg-lockup__tag{ animation: cg-lockup-fade 240ms ease var(--cg-entrance-delay,0ms) both; }
183
- @keyframes cg-lockup-fade{ from{ opacity:0; } }
184
- }
185
- `;
@@ -0,0 +1,158 @@
1
+ "use client";
2
+
3
+ import { useState, type ReactNode } from "react";
4
+ import { Button } from "./Button";
5
+
6
+ type Labels = {
7
+ /** The button that loads the map. */
8
+ open: string;
9
+ /** The outbound link ("Directions"). */
10
+ directions: string;
11
+ /** sr-only after the outbound link. */
12
+ newTab: string;
13
+ /** The note under the actions. */
14
+ note: string;
15
+ /** The close button on the bar under the open map. */
16
+ close: string;
17
+ /** Accessible title of the map frame; receives the address. */
18
+ frameTitle: (address: string) => string;
19
+ };
20
+ const EN: Labels = {
21
+ open: "View the map",
22
+ directions: "Directions",
23
+ newTab: "(opens in a new tab)",
24
+ note: "The map is Google's and only loads after your tap.",
25
+ close: "Close the map",
26
+ frameTitle: (address) => `Map: ${address}`,
27
+ };
28
+
29
+ type Props = {
30
+ /** First line, large: the street and number. */
31
+ address: string;
32
+ /** Second line: neighbourhood, city/state. */
33
+ locality?: ReactNode;
34
+ /** Still image of the map (a file of the site, never a live tile): says
35
+ * "there is a map here" without calling Google. Wide, pin near the top-middle. */
36
+ preview: { src: string; width?: number; height?: number; position?: string };
37
+ /** Embed URL, requested only after the tap (e.g. https://www.google.com/maps?q=…&output=embed). */
38
+ embedSrc: string;
39
+ /** Google Maps search/route link; works without JavaScript. */
40
+ directionsHref: string;
41
+ /** Caption on the close bar while the map is open. Default: the address. */
42
+ caption?: ReactNode;
43
+ /** Height of the open map. */
44
+ mapHeight?: string;
45
+ labels?: Partial<Labels>;
46
+ className?: string;
47
+ };
48
+
49
+ const MapPin = ({ size = 20, strokeWidth = 1.75 }: { size?: number; strokeWidth?: number }) => (
50
+ <svg aria-hidden="true" viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" /><circle cx="12" cy="10" r="3" /></svg>
51
+ );
52
+ const ArrowUpRight = () => (
53
+ <svg aria-hidden="true" viewBox="0 0 24 24" width={18} height={18} className="cg-arrow-go" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17 17 7M9 7h8v8" /></svg>
54
+ );
55
+ const X = () => (
56
+ <svg aria-hidden="true" viewBox="0 0 24 24" width={18} height={18} fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18M6 6l12 12" /></svg>
57
+ );
58
+
59
+ /**
60
+ * Map card — the Mapa of novo.creeta: a Google map that only loads on tap.
61
+ *
62
+ * The embedded map sets third-party cookies as soon as it appears, and the
63
+ * sites have no consent banner. So the card shows a still preview of the map
64
+ * fading into the card, the address, a "View the map" button and the outbound
65
+ * "Directions" link (works without JavaScript); Google is only called when the
66
+ * person asks. The page is also lighter on phones.
67
+ *
68
+ * Open, the map fills the card and a bar at the bottom (the same as the
69
+ * PageVideoHero's) carries the caption and the close button, so the person
70
+ * can put the still back.
71
+ */
72
+ export function MapCard({ address, locality, preview, embedSrc, directionsHref, caption, mapHeight = "min-h-[420px]", labels, className = "" }: Props) {
73
+ const t = { ...EN, ...labels };
74
+ const [open, setOpen] = useState(false);
75
+ const [loaded, setLoaded] = useState(false);
76
+
77
+ return (
78
+ <div className={`relative overflow-hidden rounded-2xl bg-card ${className}`}>
79
+ {open ? (
80
+ <div className="flex flex-col">
81
+ <div className={`relative ${mapHeight} bg-surface`}>
82
+ <iframe
83
+ title={t.frameTitle(address)}
84
+ src={embedSrc}
85
+ referrerPolicy="no-referrer-when-downgrade"
86
+ allowFullScreen
87
+ onLoad={() => setLoaded(true)}
88
+ // only shown once Google has drawn it, with a fade: before that the frame is an empty white
89
+ className={`absolute inset-0 block h-full w-full border-0 transition-opacity duration-300 motion-reduce:transition-none ${loaded ? "opacity-100" : "opacity-0"}`}
90
+ />
91
+ </div>
92
+ {/* close bar — the same as the hero video's: caption on the left, close on the right */}
93
+ <div className="relative z-10 flex h-[4.5rem] items-center justify-between gap-6 bg-ink px-6">
94
+ <p className="m-0 min-w-0 truncate text-sm text-surface/80">{caption ?? address}</p>
95
+ <button
96
+ type="button"
97
+ onClick={() => {
98
+ setOpen(false);
99
+ setLoaded(false);
100
+ }}
101
+ className="inline-flex min-h-11 shrink-0 items-center gap-2 rounded-full border border-surface/25 px-5 text-sm font-semibold text-surface transition-colors duration-200 hover:bg-surface/10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-surface motion-reduce:transition-none"
102
+ >
103
+ <X />
104
+ {t.close}
105
+ </button>
106
+ </div>
107
+ </div>
108
+ ) : (
109
+ <div className="flex h-full min-h-[320px] flex-col">
110
+ {/* Top: a still crop of the map fading into the card towards the bottom.
111
+ The text sits below, outside the image, always on the card color. */}
112
+ <div aria-hidden="true" className="relative min-h-[9rem] flex-1 overflow-hidden">
113
+ <img
114
+ src={preview.src}
115
+ alt=""
116
+ width={preview.width}
117
+ height={preview.height}
118
+ loading="lazy"
119
+ decoding="async"
120
+ className="absolute inset-0 h-full w-full object-cover"
121
+ style={{ objectPosition: preview.position ?? "48% 18%" }}
122
+ />
123
+ <span className="absolute inset-0 bg-gradient-to-b from-card/0 via-card/35 via-60% to-card" />
124
+ {/* on the right: on the left it covered the place's name on the map */}
125
+ <span className="absolute right-6 top-6 flex h-11 w-11 items-center justify-center rounded-full bg-card text-ink shadow-[0_2px_8px_rgba(0,0,0,0.18)] md:right-8 md:top-8">
126
+ <MapPin />
127
+ </span>
128
+ </div>
129
+
130
+ <div className="px-6 pb-6 md:px-8 md:pb-8">
131
+ <address className="m-0 not-italic font-heading text-2xl font-medium leading-snug text-ink">
132
+ {address}
133
+ {locality && <span className="mt-1 block font-body text-base font-normal text-ink/80">{locality}</span>}
134
+ </address>
135
+
136
+ <div className="mt-6 flex flex-wrap items-center gap-x-6 gap-y-2">
137
+ <Button variant="solid" onClick={() => setOpen(true)}>
138
+ <MapPin size={19} strokeWidth={2} />
139
+ {t.open}
140
+ </Button>
141
+ <a
142
+ href={directionsHref}
143
+ target="_blank"
144
+ rel="noopener noreferrer"
145
+ className="inline-flex min-h-11 items-center gap-1.5 font-semibold text-ink underline underline-offset-4 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink"
146
+ >
147
+ {t.directions}
148
+ <ArrowUpRight />
149
+ <span className="sr-only"> {t.newTab}</span>
150
+ </a>
151
+ </div>
152
+ <p className="m-0 mt-4 text-sm leading-relaxed text-ink/80">{t.note}</p>
153
+ </div>
154
+ </div>
155
+ )}
156
+ </div>
157
+ );
158
+ }