mirror of https://github.com/coder/coder.git
junk ui
This commit is contained in:
parent
094238634f
commit
0a3a01d603
|
@ -7,6 +7,7 @@ import { Outlet } from "react-router-dom";
|
|||
import { Loader } from "components/Loader/Loader";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { LicenseBanner } from "modules/dashboard/LicenseBanner/LicenseBanner";
|
||||
import { NotificationBanners } from "modules/dashboard/NotificationBanners/NotificationBanners";
|
||||
import { ServiceBanner } from "modules/dashboard/ServiceBanner/ServiceBanner";
|
||||
import { dashboardContentBottomPadding } from "theme/constants";
|
||||
import { docs } from "utils/docs";
|
||||
|
@ -23,6 +24,7 @@ export const DashboardLayout: FC = () => {
|
|||
<>
|
||||
{canViewDeployment && <LicenseBanner />}
|
||||
<ServiceBanner />
|
||||
<NotificationBanners />
|
||||
|
||||
<div
|
||||
css={{
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { NotificationBannerView } from "./NotificationBannerView";
|
||||
|
||||
const meta: Meta<typeof NotificationBannerView> = {
|
||||
title: "modules/dashboard/NotificationBannerView",
|
||||
component: NotificationBannerView,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof NotificationBannerView>;
|
||||
|
||||
export const Production: Story = {
|
||||
args: {
|
||||
message: "weeeee",
|
||||
backgroundColor: "#FFFFFF",
|
||||
},
|
||||
};
|
||||
|
||||
export const Preview: Story = {
|
||||
args: {
|
||||
message: "weeeee",
|
||||
backgroundColor: "#000000",
|
||||
},
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
import { css, type Interpolation, type Theme } from "@emotion/react";
|
||||
import type { FC } from "react";
|
||||
import { InlineMarkdown } from "components/Markdown/Markdown";
|
||||
import { readableForegroundColor } from "utils/colors";
|
||||
|
||||
export interface NotificationBannerViewProps {
|
||||
message?: string;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export const NotificationBannerView: FC<NotificationBannerViewProps> = ({
|
||||
message,
|
||||
backgroundColor,
|
||||
}) => {
|
||||
if (message === undefined || backgroundColor === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div css={[styles.banner, { backgroundColor }]} className="service-banner">
|
||||
<div
|
||||
css={[
|
||||
styles.wrapper,
|
||||
{ color: readableForegroundColor(backgroundColor) },
|
||||
]}
|
||||
>
|
||||
<InlineMarkdown>{message}</InlineMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
banner: css`
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`,
|
||||
wrapper: css`
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
font-weight: 400;
|
||||
|
||||
& a {
|
||||
color: inherit;
|
||||
}
|
||||
`,
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
|
@ -0,0 +1,22 @@
|
|||
import type { FC } from "react";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { NotificationBannerView } from "./NotificationBannerView";
|
||||
|
||||
export const NotificationBanners: FC = () => {
|
||||
const dashboard = useDashboard();
|
||||
const notificationBanners = dashboard.appearance.config.notification_banners;
|
||||
|
||||
return (
|
||||
<>
|
||||
{notificationBanners
|
||||
.filter((banner) => banner.enabled)
|
||||
.map(({ message, background_color }) => (
|
||||
<NotificationBannerView
|
||||
key={message}
|
||||
message={message}
|
||||
backgroundColor={background_color}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -30,7 +30,7 @@ const AppearanceSettingsPage: FC = () => {
|
|||
|
||||
try {
|
||||
await updateAppearanceMutation.mutateAsync(newAppearance);
|
||||
queryClient.invalidateQueries(appearanceConfigKey);
|
||||
await queryClient.invalidateQueries(appearanceConfigKey);
|
||||
displaySuccess("Successfully updated appearance settings!");
|
||||
} catch (error) {
|
||||
displayError(
|
||||
|
|
|
@ -8,7 +8,7 @@ import TextField from "@mui/material/TextField";
|
|||
import { useFormik } from "formik";
|
||||
import { type FC, useState } from "react";
|
||||
import { BlockPicker } from "react-color";
|
||||
import type { UpdateAppearanceConfig } from "api/typesGenerated";
|
||||
import type { BannerConfig, UpdateAppearanceConfig } from "api/typesGenerated";
|
||||
import {
|
||||
Badges,
|
||||
DeprecatedBadge,
|
||||
|
@ -21,6 +21,7 @@ import colors from "theme/tailwindColors";
|
|||
import { getFormHelpers } from "utils/formUtils";
|
||||
import { Fieldset } from "../Fieldset";
|
||||
import { Header } from "../Header";
|
||||
import { NotificationBannerItem } from "./NotificationBannerItem";
|
||||
|
||||
export type AppearanceSettingsPageViewProps = {
|
||||
appearance: UpdateAppearanceConfig;
|
||||
|
@ -81,6 +82,36 @@ export const AppearanceSettingsPageView: FC<
|
|||
serviceBannerForm.values.background_color,
|
||||
);
|
||||
|
||||
const [notificationBanners, setNotificationBanners] = useState(
|
||||
appearance.notification_banners,
|
||||
);
|
||||
|
||||
const addNotificationBannerItem = () => {
|
||||
setNotificationBanners((banners) => [
|
||||
...banners,
|
||||
{ enabled: true, message: "foob", background_color: "#004852" },
|
||||
]);
|
||||
};
|
||||
|
||||
const updateNotificationBannerItem = (
|
||||
i: number,
|
||||
banner: Partial<BannerConfig>,
|
||||
) => {
|
||||
setNotificationBanners((banners) => {
|
||||
const newBanners = [...banners];
|
||||
newBanners[i] = { ...banners[i], ...banner };
|
||||
return newBanners;
|
||||
});
|
||||
};
|
||||
|
||||
const removeNotificationBannerItem = (i: number) => {
|
||||
setNotificationBanners((banners) => {
|
||||
const newBanners = [...banners];
|
||||
newBanners.splice(i, 1);
|
||||
return newBanners;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
|
@ -286,24 +317,34 @@ export const AppearanceSettingsPageView: FC<
|
|||
title="Notification Banners"
|
||||
onSubmit={() =>
|
||||
onSaveAppearance({
|
||||
notification_banners: [
|
||||
{
|
||||
enabled: true,
|
||||
message: "Just a real nice test :)",
|
||||
background_color: "#ffaff3",
|
||||
},
|
||||
],
|
||||
notification_banners: notificationBanners,
|
||||
})
|
||||
}
|
||||
>
|
||||
<>
|
||||
{appearance.notification_banners
|
||||
.filter((banner) => banner.enabled)
|
||||
.map((banner) => (
|
||||
<div css={{ backgroundColor: banner.background_color }}>
|
||||
{banner.message}
|
||||
</div>
|
||||
))}
|
||||
<Button onClick={() => addNotificationBannerItem()}>+</Button>
|
||||
<Stack spacing={4}>
|
||||
{notificationBanners
|
||||
.filter((banner) => banner.enabled)
|
||||
.map((banner, i) => (
|
||||
<NotificationBannerItem
|
||||
key={i}
|
||||
enabled={banner.enabled}
|
||||
backgroundColor={banner.background_color}
|
||||
message={banner.message}
|
||||
onRemove={() => removeNotificationBannerItem(i)}
|
||||
onUpdate={(banner: Partial<BannerConfig>) => {
|
||||
const shouldPersist = "enabled" in banner;
|
||||
updateNotificationBannerItem(i, banner);
|
||||
if (shouldPersist) {
|
||||
onSaveAppearance({
|
||||
notification_banners: notificationBanners,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
</Fieldset>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { useTheme } from "@emotion/react";
|
||||
import Delete from "@mui/icons-material/Delete";
|
||||
import Button from "@mui/material/Button";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import type { FC } from "react";
|
||||
import { BlockPicker } from "react-color";
|
||||
import type { BannerConfig } from "api/typesGenerated";
|
||||
import Switch from "@mui/material/Switch";
|
||||
|
||||
interface NotificationBannerItemProps {
|
||||
enabled: boolean;
|
||||
backgroundColor?: string;
|
||||
message?: string;
|
||||
onRemove: () => void;
|
||||
onUpdate: (banner: Partial<BannerConfig>) => void;
|
||||
}
|
||||
|
||||
export const NotificationBannerItem: FC<NotificationBannerItemProps> = ({
|
||||
enabled,
|
||||
backgroundColor,
|
||||
message,
|
||||
onRemove,
|
||||
onUpdate,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={() => onUpdate({ enabled: !enabled })}
|
||||
data-testid="switch-service-banner"
|
||||
/>
|
||||
<Button onClick={onRemove}>
|
||||
<Delete />
|
||||
</Button>
|
||||
</div>
|
||||
<div css={{ backgroundColor }}>{message}</div>
|
||||
|
||||
<TextField
|
||||
// {...serviceBannerFieldHelpers("message", {
|
||||
// helperText:
|
||||
// ,
|
||||
// })}
|
||||
onChange={(event) => onUpdate({ message: event.target.value })}
|
||||
defaultValue={message}
|
||||
helperText="Markdown bold, italics, and links are supported."
|
||||
fullWidth
|
||||
label="Message"
|
||||
multiline
|
||||
inputProps={{
|
||||
"aria-label": "Message",
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
css={{
|
||||
backgroundColor,
|
||||
width: 24,
|
||||
height: 24,
|
||||
outline: "none",
|
||||
border: "none",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
></button>
|
||||
<details>
|
||||
<summary>Background Color</summary>
|
||||
<BlockPicker
|
||||
color={backgroundColor}
|
||||
onChange={async (color) => {
|
||||
// TODO: preview the color?
|
||||
onUpdate({ background_color: color.hex });
|
||||
}}
|
||||
triangle="hide"
|
||||
colors={["#004852", "#D65D0F", "#4CD473", "#D94A5D", "#5A00CF"]}
|
||||
styles={{
|
||||
default: {
|
||||
input: {
|
||||
color: "white",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
body: {
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "black",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue