- Notifications
You must be signed in to change notification settings - Fork924
feat: implement multi-org template gallery#13784
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
0a5c431
6f96fee
51ebb67
a0effc6
be37085
5649166
3ea3aa2
f17a0c3
0077db0
461202e
66e02fb
369c59f
c41cdc4
7f5d35e
6e2a6d8
978c047
aaed038
8d84ad9
a1c6169
15542c0
8f4c56f
a282bac
b092644
32376e6
801138a
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { chromatic } from "testHelpers/chromatic"; | ||
import { MockTemplate } from "testHelpers/entities"; | ||
import { TemplateCard } from "./TemplateCard"; | ||
const meta: Meta<typeof TemplateCard> = { | ||
title: "modules/templates/TemplateCard", | ||
parameters: { chromatic }, | ||
component: TemplateCard, | ||
args: { | ||
template: MockTemplate, | ||
}, | ||
}; | ||
export default meta; | ||
type Story = StoryObj<typeof TemplateCard>; | ||
export const Template: Story = {}; | ||
export const DeprecatedTemplate: Story = { | ||
args: { | ||
template: { | ||
...MockTemplate, | ||
deprecated: true, | ||
}, | ||
}, | ||
}; | ||
export const LongContentTemplate: Story = { | ||
args: { | ||
template: { | ||
...MockTemplate, | ||
display_name: "Very Long Template Name", | ||
organization_display_name: "Very Long Organization Name", | ||
description: | ||
"This is a very long test description. This is a very long test description. This is a very long test description. This is a very long test description", | ||
active_user_count: 999, | ||
}, | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import type { Interpolation, Theme } from "@emotion/react"; | ||
import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined"; | ||
import Button from "@mui/material/Button"; | ||
import type { FC, HTMLAttributes } from "react"; | ||
import { Link as RouterLink, useNavigate } from "react-router-dom"; | ||
import type { Template } from "api/typesGenerated"; | ||
import { ExternalAvatar } from "components/Avatar/Avatar"; | ||
import { AvatarData } from "components/AvatarData/AvatarData"; | ||
import { DeprecatedBadge } from "components/Badges/Badges"; | ||
type TemplateCardProps = HTMLAttributes<HTMLDivElement> & { | ||
template: Template; | ||
}; | ||
export const TemplateCard: FC<TemplateCardProps> = ({ | ||
template, | ||
...divProps | ||
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. super minor nit, I tend to call these sorts of captures 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. I was following the pattern in the files I was working on. In other code bases/the React docs, this is often called ...props as well. I think it depends if we want to call them based on what they are, "props" or how they are used, props on divs "divProps" or attributes on divs "attrs" | ||
}) => { | ||
const navigate = useNavigate(); | ||
const templatePageLink = `/templates/${template.name}`; | ||
const hasIcon = template.icon && template.icon !== ""; | ||
const handleKeyDown = (e: React.KeyboardEvent) => { | ||
if (e.key === "Enter" && e.currentTarget === e.target) { | ||
navigate(templatePageLink); | ||
} | ||
}; | ||
return ( | ||
<div | ||
css={styles.card} | ||
{...divProps} | ||
role="button" | ||
tabIndex={0} | ||
onClick={() => navigate(templatePageLink)} | ||
onKeyDown={handleKeyDown} | ||
> | ||
<div css={styles.header}> | ||
<div> | ||
<AvatarData | ||
title={ | ||
template.display_name.length > 0 | ||
? template.display_name | ||
: template.name | ||
} | ||
subtitle={template.organization_display_name} | ||
avatar={ | ||
hasIcon && ( | ||
<ExternalAvatar variant="square" fitImage src={template.icon} /> | ||
) | ||
} | ||
/> | ||
</div> | ||
<div> | ||
{template.active_user_count}{" "} | ||
{template.active_user_count === 1 ? "user" : "users"} | ||
</div> | ||
</div> | ||
<div> | ||
<span css={styles.description}> | ||
<p>{template.description}</p> | ||
</span> | ||
</div> | ||
<div css={styles.useButtonContainer}> | ||
{template.deprecated ? ( | ||
<DeprecatedBadge /> | ||
) : ( | ||
<Button | ||
component={RouterLink} | ||
css={styles.actionButton} | ||
className="actionButton" | ||
fullWidth | ||
startIcon={<ArrowForwardOutlined />} | ||
title={`Create a workspace using the ${template.display_name} template`} | ||
to={`/templates/${template.name}/workspace`} | ||
onClick={(e) => { | ||
e.stopPropagation(); | ||
}} | ||
> | ||
Create Workspace | ||
</Button> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
const styles = { | ||
card: (theme) => ({ | ||
width: "320px", | ||
padding: 24, | ||
borderRadius: 6, | ||
border: `1px solid ${theme.palette.divider}`, | ||
textAlign: "left", | ||
color: "inherit", | ||
display: "flex", | ||
flexDirection: "column", | ||
cursor: "pointer", | ||
"&:hover": { | ||
color: theme.experimental.l2.hover.text, | ||
borderColor: theme.experimental.l2.hover.text, | ||
}, | ||
}), | ||
header: { | ||
display: "flex", | ||
alignItems: "center", | ||
justifyContent: "space-between", | ||
marginBottom: 24, | ||
}, | ||
icon: { | ||
flexShrink: 0, | ||
paddingTop: 4, | ||
width: 32, | ||
height: 32, | ||
}, | ||
description: (theme) => ({ | ||
fontSize: 13, | ||
color: theme.palette.text.secondary, | ||
lineHeight: "1.6", | ||
display: "block", | ||
}), | ||
useButtonContainer: { | ||
display: "flex", | ||
gap: 12, | ||
flexDirection: "column", | ||
paddingTop: 24, | ||
marginTop: "auto", | ||
alignItems: "center", | ||
}, | ||
actionButton: (theme) => ({ | ||
transition: "none", | ||
color: theme.palette.text.secondary, | ||
"&:hover": { | ||
borderColor: theme.palette.text.primary, | ||
}, | ||
}), | ||
} satisfies Record<string, Interpolation<Theme>>; |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.