@canva/cli 1.11.0 → 1.13.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.
- package/CHANGELOG.md +43 -0
- package/README.md +13 -0
- package/cli.js +593 -580
- package/lib/cjs/index.cjs +3 -3
- package/lib/esm/index.mjs +4 -4
- package/package.json +7 -2
- package/templates/base/package.json +7 -7
- package/templates/base/webpack.config.ts +0 -1
- package/templates/common/jest.config.mjs +1 -1
- package/templates/dam/backend/server.ts +8 -0
- package/templates/dam/canva-app.json +4 -0
- package/templates/dam/package.json +7 -7
- package/templates/dam/webpack.config.ts +0 -1
- package/templates/data_connector/README.md +1 -1
- package/templates/data_connector/package.json +7 -7
- package/templates/data_connector/webpack.config.ts +0 -1
- package/templates/gen_ai/backend/server.ts +17 -0
- package/templates/gen_ai/package.json +7 -7
- package/templates/gen_ai/src/api/api.ts +4 -0
- package/templates/gen_ai/webpack.config.ts +0 -1
- package/templates/hello_world/package.json +7 -7
- package/templates/hello_world/webpack.config.ts +0 -1
- package/templates/mls/README.md +81 -0
- package/templates/mls/canva-app.json +25 -0
- package/templates/mls/declarations/declarations.d.ts +29 -0
- package/templates/mls/eslint.config.mjs +14 -0
- package/templates/mls/jest.config.mjs +36 -0
- package/templates/mls/jest.setup.ts +37 -0
- package/templates/mls/package.json +117 -0
- package/templates/mls/scripts/copy_env.ts +13 -0
- package/templates/mls/scripts/ssl/ssl.ts +131 -0
- package/templates/mls/scripts/start/app_runner.ts +223 -0
- package/templates/mls/scripts/start/context.ts +171 -0
- package/templates/mls/scripts/start/start.ts +46 -0
- package/templates/mls/src/__tests__/app.tests.tsx +11 -0
- package/templates/mls/src/__tests__/office_selection_page.tests.tsx +72 -0
- package/templates/mls/src/__tests__/utils.tsx +19 -0
- package/templates/mls/src/adapter.ts +126 -0
- package/templates/mls/src/components/agent/agent_card.tsx +57 -0
- package/templates/mls/src/components/agent/agent_grid.tsx +37 -0
- package/templates/mls/src/components/agent/agent_list.tsx +17 -0
- package/templates/mls/src/components/agent/agent_search_filters.tsx +88 -0
- package/templates/mls/src/components/breadcrumb/breadcrumb.tsx +40 -0
- package/templates/mls/src/components/listing/listing_card.tsx +64 -0
- package/templates/mls/src/components/listing/listing_grid.tsx +37 -0
- package/templates/mls/src/components/listing/listing_list.tsx +21 -0
- package/templates/mls/src/components/listing/listing_search_filters.tsx +145 -0
- package/templates/mls/src/components/placeholders/placeholders.tsx +65 -0
- package/templates/mls/src/data.ts +359 -0
- package/templates/mls/src/index.tsx +4 -0
- package/templates/mls/src/intents/design_editor/app.tsx +44 -0
- package/templates/mls/src/intents/design_editor/index.tsx +25 -0
- package/templates/mls/src/pages/agent_details_page/agent_details_page.tsx +175 -0
- package/templates/mls/src/pages/list_page/agent_tab_panel.tsx +126 -0
- package/templates/mls/src/pages/list_page/list_page.tsx +67 -0
- package/templates/mls/src/pages/list_page/listing_tab_panel.tsx +135 -0
- package/templates/mls/src/pages/listing_details_page/listing_details_page.tsx +418 -0
- package/templates/mls/src/pages/loading_page/loading_page.tsx +152 -0
- package/templates/mls/src/pages/office_selection_page/office_selection_page.tsx +144 -0
- package/templates/mls/src/real_estate.type.ts +44 -0
- package/templates/mls/src/util/use_add_element.tsx +62 -0
- package/templates/mls/src/util/use_drag_element.tsx +68 -0
- package/templates/mls/styles/components.css +56 -0
- package/templates/mls/tsconfig.json +55 -0
- package/templates/mls/webpack.config.ts +253 -0
- package/templates/base/backend/routers/oauth.ts +0 -393
- package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +0 -99
- package/templates/base/utils/backend/bearer_middleware/index.ts +0 -1
- package/templates/base/utils/backend/bearer_middleware/tests/bearer_middleware.tests.ts +0 -192
- package/templates/common/utils/backend/base_backend/create.ts +0 -104
- package/templates/gen_ai/backend/database/database.ts +0 -42
- package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +0 -99
- package/templates/gen_ai/utils/backend/bearer_middleware/index.ts +0 -1
- /package/templates/base/{utils/backend → backend}/base_backend/create.ts +0 -0
- /package/templates/base/{utils/backend → backend}/jwt_middleware/index.ts +0 -0
- /package/templates/base/{utils/backend → backend}/jwt_middleware/jwt_middleware.ts +0 -0
- /package/templates/{common → gen_ai}/utils/backend/jwt_middleware/index.ts +0 -0
- /package/templates/{common → gen_ai}/utils/backend/jwt_middleware/jwt_middleware.ts +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { HorizontalCard, ImageCard } from "@canva/app-ui-kit";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import type { Agent } from "../../real_estate.type";
|
|
5
|
+
|
|
6
|
+
interface AgentCardProps {
|
|
7
|
+
item: Agent;
|
|
8
|
+
onClick: (item: Agent) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ListAgentCard: React.FC<AgentCardProps> = ({ item, onClick }) => {
|
|
12
|
+
const intl = useIntl();
|
|
13
|
+
const headshot = item.headshots?.[0];
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<HorizontalCard
|
|
17
|
+
ariaLabel={item.name}
|
|
18
|
+
title={item.name}
|
|
19
|
+
description={item.officeId}
|
|
20
|
+
onClick={() => onClick(item)}
|
|
21
|
+
thumbnail={
|
|
22
|
+
headshot
|
|
23
|
+
? {
|
|
24
|
+
url: headshot.url,
|
|
25
|
+
alt: intl.formatMessage(
|
|
26
|
+
{
|
|
27
|
+
defaultMessage: "Profile photo of {name}",
|
|
28
|
+
description: "Alt text for agent profile photo",
|
|
29
|
+
},
|
|
30
|
+
{ name: item.name },
|
|
31
|
+
),
|
|
32
|
+
}
|
|
33
|
+
: undefined
|
|
34
|
+
}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const GridAgentCard: React.FC<AgentCardProps> = ({ item, onClick }) => {
|
|
40
|
+
const intl = useIntl();
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<ImageCard
|
|
44
|
+
selectable
|
|
45
|
+
thumbnailUrl={item.headshots?.[0]?.url || ""}
|
|
46
|
+
alt={intl.formatMessage(
|
|
47
|
+
{
|
|
48
|
+
defaultMessage: "Profile photo of {name}",
|
|
49
|
+
description: "Alt text for agent profile photo",
|
|
50
|
+
},
|
|
51
|
+
{ name: item.name },
|
|
52
|
+
)}
|
|
53
|
+
thumbnailHeight={150}
|
|
54
|
+
onClick={() => onClick(item)}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Column, Columns, Rows } from "@canva/app-ui-kit";
|
|
2
|
+
import type { Agent } from "../../real_estate.type";
|
|
3
|
+
import { GridAgentCard } from "./agent_card";
|
|
4
|
+
|
|
5
|
+
interface AgentGridProps {
|
|
6
|
+
agents: Agent[];
|
|
7
|
+
onAgentClick: (item: Agent) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const AgentGrid = ({ agents, onAgentClick }: AgentGridProps) => {
|
|
11
|
+
return (
|
|
12
|
+
<Rows spacing="2u">
|
|
13
|
+
{agents.map((item, index) => {
|
|
14
|
+
if (index % 2 === 0) {
|
|
15
|
+
const nextItem = agents[index + 1];
|
|
16
|
+
return (
|
|
17
|
+
<Columns
|
|
18
|
+
key={`row-${Math.floor(index / 2)}`}
|
|
19
|
+
spacing="2u"
|
|
20
|
+
alignY="stretch"
|
|
21
|
+
>
|
|
22
|
+
<Column key={item.id} width="1/2">
|
|
23
|
+
<GridAgentCard item={item} onClick={onAgentClick} />
|
|
24
|
+
</Column>
|
|
25
|
+
{nextItem && (
|
|
26
|
+
<Column key={nextItem.id} width="1/2">
|
|
27
|
+
<GridAgentCard item={nextItem} onClick={onAgentClick} />
|
|
28
|
+
</Column>
|
|
29
|
+
)}
|
|
30
|
+
</Columns>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
})}
|
|
35
|
+
</Rows>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Agent } from "../../real_estate.type";
|
|
2
|
+
import { ListAgentCard } from "./agent_card";
|
|
3
|
+
|
|
4
|
+
interface AgentListProps {
|
|
5
|
+
agents: Agent[];
|
|
6
|
+
onAgentClick: (item: Agent) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AgentList = ({ agents, onAgentClick }: AgentListProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
{agents.map((item: Agent) => (
|
|
13
|
+
<ListAgentCard key={item.id} item={item} onClick={onAgentClick} />
|
|
14
|
+
))}
|
|
15
|
+
</>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Column,
|
|
4
|
+
Columns,
|
|
5
|
+
GridViewIcon,
|
|
6
|
+
ListBulletLtrIcon,
|
|
7
|
+
Rows,
|
|
8
|
+
SearchInputMenu,
|
|
9
|
+
Select,
|
|
10
|
+
} from "@canva/app-ui-kit";
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import { useIntl } from "react-intl";
|
|
13
|
+
|
|
14
|
+
type Layout = "grid" | "list";
|
|
15
|
+
|
|
16
|
+
interface AgentSearchFiltersProps {
|
|
17
|
+
query: string;
|
|
18
|
+
onQueryChange: (query: string) => void;
|
|
19
|
+
layout: Layout;
|
|
20
|
+
onLayoutToggle: () => void;
|
|
21
|
+
sort: string;
|
|
22
|
+
onSortChange: (sort: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const AgentSearchFilters = ({
|
|
26
|
+
query,
|
|
27
|
+
onQueryChange,
|
|
28
|
+
layout,
|
|
29
|
+
onLayoutToggle,
|
|
30
|
+
sort,
|
|
31
|
+
onSortChange,
|
|
32
|
+
}: AgentSearchFiltersProps) => {
|
|
33
|
+
const intl = useIntl();
|
|
34
|
+
const [queryValue, setQueryValue] = useState(query);
|
|
35
|
+
return (
|
|
36
|
+
<Rows spacing="2u">
|
|
37
|
+
<SearchInputMenu
|
|
38
|
+
value={queryValue}
|
|
39
|
+
onChange={setQueryValue}
|
|
40
|
+
onChangeComplete={() => onQueryChange(queryValue)}
|
|
41
|
+
onClear={() => {
|
|
42
|
+
setQueryValue("");
|
|
43
|
+
onQueryChange("");
|
|
44
|
+
}}
|
|
45
|
+
placeholder={intl.formatMessage({
|
|
46
|
+
defaultMessage: "Search agents...",
|
|
47
|
+
description: "Placeholder text for agents search input",
|
|
48
|
+
})}
|
|
49
|
+
/>
|
|
50
|
+
<Columns spacing="1u" alignY="center">
|
|
51
|
+
<Column width="content">
|
|
52
|
+
<Button
|
|
53
|
+
icon={layout === "grid" ? GridViewIcon : ListBulletLtrIcon}
|
|
54
|
+
type="button"
|
|
55
|
+
onClick={onLayoutToggle}
|
|
56
|
+
variant="secondary"
|
|
57
|
+
/>
|
|
58
|
+
</Column>
|
|
59
|
+
<Column width="1/3">
|
|
60
|
+
<Select
|
|
61
|
+
value={sort}
|
|
62
|
+
onChange={onSortChange}
|
|
63
|
+
options={[
|
|
64
|
+
{
|
|
65
|
+
label: intl.formatMessage({
|
|
66
|
+
defaultMessage: "Name: A to Z",
|
|
67
|
+
description: "Sort option - name ascending",
|
|
68
|
+
}),
|
|
69
|
+
value: "name-asc",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
label: intl.formatMessage({
|
|
73
|
+
defaultMessage: "Name: Z to A",
|
|
74
|
+
description: "Sort option - name descending",
|
|
75
|
+
}),
|
|
76
|
+
value: "name-desc",
|
|
77
|
+
},
|
|
78
|
+
]}
|
|
79
|
+
placeholder={intl.formatMessage({
|
|
80
|
+
defaultMessage: "Sort by",
|
|
81
|
+
description: "Placeholder text for agents sort dropdown",
|
|
82
|
+
})}
|
|
83
|
+
/>
|
|
84
|
+
</Column>
|
|
85
|
+
</Columns>
|
|
86
|
+
</Rows>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrowLeftIcon,
|
|
3
|
+
Button,
|
|
4
|
+
Column,
|
|
5
|
+
Columns,
|
|
6
|
+
Text,
|
|
7
|
+
} from "@canva/app-ui-kit";
|
|
8
|
+
import { useIntl } from "react-intl";
|
|
9
|
+
import { useLocation, useNavigate } from "react-router-dom";
|
|
10
|
+
import type { Office } from "../../real_estate.type";
|
|
11
|
+
|
|
12
|
+
export const Breadcrumb = () => {
|
|
13
|
+
const office = (useLocation().state as { office: Office })?.office;
|
|
14
|
+
const navigate = useNavigate();
|
|
15
|
+
const intl = useIntl();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Columns spacing="1u" alignY="center">
|
|
19
|
+
<Column width="content">
|
|
20
|
+
<Button
|
|
21
|
+
icon={ArrowLeftIcon}
|
|
22
|
+
size="small"
|
|
23
|
+
type="button"
|
|
24
|
+
variant="tertiary"
|
|
25
|
+
onClick={() => navigate("/entry")}
|
|
26
|
+
/>
|
|
27
|
+
</Column>
|
|
28
|
+
<Column>
|
|
29
|
+
<Text variant="bold">
|
|
30
|
+
{office
|
|
31
|
+
? office.name
|
|
32
|
+
: intl.formatMessage({
|
|
33
|
+
defaultMessage: "Back",
|
|
34
|
+
description: "Back button",
|
|
35
|
+
})}
|
|
36
|
+
</Text>
|
|
37
|
+
</Column>
|
|
38
|
+
</Columns>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { HorizontalCard, ImageCard, Rows, Text } from "@canva/app-ui-kit";
|
|
2
|
+
import type { Property } from "../../real_estate.type";
|
|
3
|
+
|
|
4
|
+
export interface ListingCardProps<T extends Property> {
|
|
5
|
+
item: T;
|
|
6
|
+
onClick?: (item: T) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface BaseListingCardProps<T extends Property> {
|
|
10
|
+
item: T;
|
|
11
|
+
onClick?: (item: T) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const GridListingCard = <T extends Property>({
|
|
15
|
+
item,
|
|
16
|
+
onClick,
|
|
17
|
+
}: BaseListingCardProps<T>) => {
|
|
18
|
+
const handleClick = () => {
|
|
19
|
+
onClick?.(item);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<Rows spacing="1u">
|
|
25
|
+
<ImageCard
|
|
26
|
+
selectable
|
|
27
|
+
thumbnailUrl={item.thumbnail.url}
|
|
28
|
+
alt={item.description}
|
|
29
|
+
borderRadius="standard"
|
|
30
|
+
onClick={handleClick}
|
|
31
|
+
thumbnailHeight={110}
|
|
32
|
+
/>
|
|
33
|
+
<Rows spacing="0">
|
|
34
|
+
<Text variant="bold" lineClamp={1}>
|
|
35
|
+
{item.title}
|
|
36
|
+
</Text>
|
|
37
|
+
{item.suburb && <Text size="small">{item.suburb}</Text>}
|
|
38
|
+
</Rows>
|
|
39
|
+
</Rows>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const ListListingCard = <T extends Property>({
|
|
45
|
+
item,
|
|
46
|
+
onClick,
|
|
47
|
+
}: BaseListingCardProps<T>) => {
|
|
48
|
+
const handleClick = () => {
|
|
49
|
+
onClick?.(item);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<HorizontalCard
|
|
54
|
+
ariaLabel={item.title}
|
|
55
|
+
title={item.title}
|
|
56
|
+
description={item.suburb}
|
|
57
|
+
onClick={handleClick}
|
|
58
|
+
thumbnail={{
|
|
59
|
+
url: item.thumbnail.url,
|
|
60
|
+
alt: item.description,
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Column, Columns, Rows } from "@canva/app-ui-kit";
|
|
2
|
+
import type { Property } from "../../real_estate.type";
|
|
3
|
+
import { GridListingCard } from "./listing_card";
|
|
4
|
+
|
|
5
|
+
interface ListingGridProps {
|
|
6
|
+
listings: Property[];
|
|
7
|
+
onListingClick: (item: Property) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ListingGrid = ({ listings, onListingClick }: ListingGridProps) => {
|
|
11
|
+
return (
|
|
12
|
+
<Rows spacing="2u">
|
|
13
|
+
{listings.map((item, index) => {
|
|
14
|
+
if (index % 2 === 0) {
|
|
15
|
+
const nextItem = listings[index + 1];
|
|
16
|
+
return (
|
|
17
|
+
<Columns
|
|
18
|
+
key={`row-${Math.floor(index / 2)}`}
|
|
19
|
+
spacing="2u"
|
|
20
|
+
alignY="stretch"
|
|
21
|
+
>
|
|
22
|
+
<Column key={item.id} width="1/2">
|
|
23
|
+
<GridListingCard item={item} onClick={onListingClick} />
|
|
24
|
+
</Column>
|
|
25
|
+
{nextItem && (
|
|
26
|
+
<Column key={nextItem.id} width="1/2">
|
|
27
|
+
<GridListingCard item={nextItem} onClick={onListingClick} />
|
|
28
|
+
</Column>
|
|
29
|
+
)}
|
|
30
|
+
</Columns>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
})}
|
|
35
|
+
</Rows>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Property } from "../../real_estate.type";
|
|
2
|
+
import { ListListingCard } from "./listing_card";
|
|
3
|
+
|
|
4
|
+
interface ListingListProps {
|
|
5
|
+
listings: Property[];
|
|
6
|
+
onListingClick: (item: Property) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const ListingList = ({ listings, onListingClick }: ListingListProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
{listings.map((item: Property, index: number) => (
|
|
13
|
+
<ListListingCard
|
|
14
|
+
key={`${item.id}-${index}`}
|
|
15
|
+
item={item}
|
|
16
|
+
onClick={onListingClick}
|
|
17
|
+
/>
|
|
18
|
+
))}
|
|
19
|
+
</>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Column,
|
|
4
|
+
Columns,
|
|
5
|
+
GridViewIcon,
|
|
6
|
+
ListBulletLtrIcon,
|
|
7
|
+
Rows,
|
|
8
|
+
SearchInputMenu,
|
|
9
|
+
Select,
|
|
10
|
+
} from "@canva/app-ui-kit";
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import { useIntl } from "react-intl";
|
|
13
|
+
|
|
14
|
+
type Layout = "grid" | "list";
|
|
15
|
+
|
|
16
|
+
interface ListingSearchFiltersProps {
|
|
17
|
+
query: string;
|
|
18
|
+
onQueryChange: (query: string) => void;
|
|
19
|
+
layout: Layout;
|
|
20
|
+
onLayoutToggle: () => void;
|
|
21
|
+
propertyType: string;
|
|
22
|
+
onPropertyTypeChange: (type: string) => void;
|
|
23
|
+
sort: string;
|
|
24
|
+
onSortChange: (sort: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const ListingSearchFilters = ({
|
|
28
|
+
query,
|
|
29
|
+
onQueryChange,
|
|
30
|
+
layout,
|
|
31
|
+
onLayoutToggle,
|
|
32
|
+
propertyType,
|
|
33
|
+
onPropertyTypeChange,
|
|
34
|
+
sort,
|
|
35
|
+
onSortChange,
|
|
36
|
+
}: ListingSearchFiltersProps) => {
|
|
37
|
+
const intl = useIntl();
|
|
38
|
+
const [queryValue, setQueryValue] = useState(query);
|
|
39
|
+
return (
|
|
40
|
+
<Rows spacing="2u">
|
|
41
|
+
<SearchInputMenu
|
|
42
|
+
value={queryValue}
|
|
43
|
+
onChange={setQueryValue}
|
|
44
|
+
onChangeComplete={() => onQueryChange(queryValue)}
|
|
45
|
+
onClear={() => {
|
|
46
|
+
setQueryValue("");
|
|
47
|
+
onQueryChange("");
|
|
48
|
+
}}
|
|
49
|
+
placeholder={intl.formatMessage({
|
|
50
|
+
defaultMessage: "Search listings...",
|
|
51
|
+
description: "Placeholder text for listings search input",
|
|
52
|
+
})}
|
|
53
|
+
/>
|
|
54
|
+
<Columns spacing="1u" alignY="center">
|
|
55
|
+
<Column width="content">
|
|
56
|
+
<Button
|
|
57
|
+
icon={layout === "grid" ? GridViewIcon : ListBulletLtrIcon}
|
|
58
|
+
type="button"
|
|
59
|
+
onClick={onLayoutToggle}
|
|
60
|
+
variant="secondary"
|
|
61
|
+
ariaLabel={intl.formatMessage(
|
|
62
|
+
{
|
|
63
|
+
defaultMessage:
|
|
64
|
+
"Toggle layout between grid and list. Current layout is {currentLayout}",
|
|
65
|
+
description: "Aria label for layout toggle button",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
currentLayout:
|
|
69
|
+
layout === "grid"
|
|
70
|
+
? intl.formatMessage({
|
|
71
|
+
defaultMessage: "grid",
|
|
72
|
+
description: "Layout option",
|
|
73
|
+
})
|
|
74
|
+
: intl.formatMessage({
|
|
75
|
+
defaultMessage: "list",
|
|
76
|
+
description: "Layout option",
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
81
|
+
</Column>
|
|
82
|
+
<Column width="1/3">
|
|
83
|
+
{/* TODO (App Developer): Review filter options and update to match your specific property data shape */}
|
|
84
|
+
<Select
|
|
85
|
+
value={propertyType}
|
|
86
|
+
onChange={onPropertyTypeChange}
|
|
87
|
+
options={[
|
|
88
|
+
{
|
|
89
|
+
label: intl.formatMessage({
|
|
90
|
+
defaultMessage: "House",
|
|
91
|
+
description: "Property type option - house",
|
|
92
|
+
}),
|
|
93
|
+
value: "house",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: intl.formatMessage({
|
|
97
|
+
defaultMessage: "Apartment",
|
|
98
|
+
description: "Property type option - apartment",
|
|
99
|
+
}),
|
|
100
|
+
value: "apartment",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
label: intl.formatMessage({
|
|
104
|
+
defaultMessage: "Townhouse",
|
|
105
|
+
description: "Property type option - townhouse",
|
|
106
|
+
}),
|
|
107
|
+
value: "townhouse",
|
|
108
|
+
},
|
|
109
|
+
]}
|
|
110
|
+
placeholder={intl.formatMessage({
|
|
111
|
+
defaultMessage: "Property type",
|
|
112
|
+
description: "Placeholder text for property type dropdown",
|
|
113
|
+
})}
|
|
114
|
+
/>
|
|
115
|
+
</Column>
|
|
116
|
+
<Column width="1/3">
|
|
117
|
+
<Select
|
|
118
|
+
value={sort}
|
|
119
|
+
onChange={onSortChange}
|
|
120
|
+
options={[
|
|
121
|
+
{
|
|
122
|
+
label: intl.formatMessage({
|
|
123
|
+
defaultMessage: "Price: Low to High",
|
|
124
|
+
description: "Sort option - price ascending",
|
|
125
|
+
}),
|
|
126
|
+
value: "price-asc",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
label: intl.formatMessage({
|
|
130
|
+
defaultMessage: "Price: High to Low",
|
|
131
|
+
description: "Sort option - price descending",
|
|
132
|
+
}),
|
|
133
|
+
value: "price-desc",
|
|
134
|
+
},
|
|
135
|
+
]}
|
|
136
|
+
placeholder={intl.formatMessage({
|
|
137
|
+
defaultMessage: "Sort by",
|
|
138
|
+
description: "Placeholder text for sort dropdown",
|
|
139
|
+
})}
|
|
140
|
+
/>
|
|
141
|
+
</Column>
|
|
142
|
+
</Columns>
|
|
143
|
+
</Rows>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Column,
|
|
4
|
+
Columns,
|
|
5
|
+
Placeholder,
|
|
6
|
+
Rows,
|
|
7
|
+
TextPlaceholder,
|
|
8
|
+
TitlePlaceholder,
|
|
9
|
+
} from "@canva/app-ui-kit";
|
|
10
|
+
import React from "react";
|
|
11
|
+
|
|
12
|
+
export const ListPlaceholder = () => (
|
|
13
|
+
<Columns spacing="2u" align="start" alignY="stretch">
|
|
14
|
+
<Column width="content">
|
|
15
|
+
<Box width="full" height="full">
|
|
16
|
+
<div
|
|
17
|
+
style={{
|
|
18
|
+
height: "60px",
|
|
19
|
+
width: "60px",
|
|
20
|
+
paddingLeft: "var(--ui-kit-space-050)",
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
<Placeholder shape="rectangle" />
|
|
24
|
+
</div>
|
|
25
|
+
</Box>
|
|
26
|
+
</Column>
|
|
27
|
+
<Column width="fluid">
|
|
28
|
+
<Rows spacing="0">
|
|
29
|
+
<TitlePlaceholder />
|
|
30
|
+
<TextPlaceholder />
|
|
31
|
+
</Rows>
|
|
32
|
+
</Column>
|
|
33
|
+
</Columns>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export const GridPlaceholder = () => (
|
|
37
|
+
<Columns spacing="2u" alignY="stretch">
|
|
38
|
+
<Column width="1/2">
|
|
39
|
+
<Rows spacing="1u">
|
|
40
|
+
<Box width="full" height="full">
|
|
41
|
+
<div style={{ height: "120px" }}>
|
|
42
|
+
<Placeholder shape="rectangle" />
|
|
43
|
+
</div>
|
|
44
|
+
</Box>
|
|
45
|
+
<Rows spacing="0">
|
|
46
|
+
<TitlePlaceholder />
|
|
47
|
+
<TitlePlaceholder size="small" />
|
|
48
|
+
</Rows>
|
|
49
|
+
</Rows>
|
|
50
|
+
</Column>
|
|
51
|
+
<Column width="1/2">
|
|
52
|
+
<Rows spacing="1u">
|
|
53
|
+
<Box width="full" height="full">
|
|
54
|
+
<div style={{ height: "120px" }}>
|
|
55
|
+
<Placeholder shape="rectangle" />
|
|
56
|
+
</div>
|
|
57
|
+
</Box>
|
|
58
|
+
<Rows spacing="0">
|
|
59
|
+
<TitlePlaceholder />
|
|
60
|
+
<TitlePlaceholder size="small" />
|
|
61
|
+
</Rows>
|
|
62
|
+
</Rows>
|
|
63
|
+
</Column>
|
|
64
|
+
</Columns>
|
|
65
|
+
);
|