Generic view header (#115)
* implements static template for table view header * initial table view header layout * renamed * added actions * format * review Co-authored-by: Sarah Rambacher <srambach@redhat.com>
This commit is contained in:
parent
e7b108a623
commit
19b458577b
8 changed files with 205 additions and 64 deletions
|
@ -10,6 +10,7 @@ import { HttpClientContext } from "../http-service/HttpClientContext";
|
|||
import { KeycloakContext } from "../auth/KeycloakContext";
|
||||
import { ClientRepresentation } from "./models/client-model";
|
||||
import { RealmContext } from "../components/realm-context/RealmContext";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
|
||||
export const ClientsSection = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
|
@ -28,40 +29,46 @@ export const ClientsSection = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<PageSection variant="light">
|
||||
<DataLoader loader={loader}>
|
||||
{(clients) => (
|
||||
<TableToolbar
|
||||
count={clients!.length}
|
||||
first={first}
|
||||
max={max}
|
||||
onNextClick={setFirst}
|
||||
onPreviousClick={setFirst}
|
||||
onPerPageSelect={(f, m) => {
|
||||
setFirst(f);
|
||||
setMax(m);
|
||||
}}
|
||||
toolbarItem={
|
||||
<>
|
||||
<Button onClick={() => history.push("/add-client")}>
|
||||
{t("createClient")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => history.push("/import-client")}
|
||||
variant="link"
|
||||
>
|
||||
{t("importClient")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ClientList
|
||||
clients={clients}
|
||||
baseUrl={keycloak!.authServerUrl()!}
|
||||
/>
|
||||
</TableToolbar>
|
||||
)}
|
||||
</DataLoader>
|
||||
</PageSection>
|
||||
<>
|
||||
<ViewHeader
|
||||
titleKey="clients:clientList"
|
||||
subKey="clients:clientsExplain"
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<DataLoader loader={loader}>
|
||||
{(clients) => (
|
||||
<TableToolbar
|
||||
count={clients!.length}
|
||||
first={first}
|
||||
max={max}
|
||||
onNextClick={setFirst}
|
||||
onPreviousClick={setFirst}
|
||||
onPerPageSelect={(first, max) => {
|
||||
setFirst(first);
|
||||
setMax(max);
|
||||
}}
|
||||
toolbarItem={
|
||||
<>
|
||||
<Button onClick={() => history.push("/add-client")}>
|
||||
{t("createClient")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => history.push("/import-client")}
|
||||
variant="link"
|
||||
>
|
||||
{t("importClient")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ClientList
|
||||
clients={clients}
|
||||
baseUrl={keycloak!.authServerUrl()!}
|
||||
/>
|
||||
</TableToolbar>
|
||||
)}
|
||||
</DataLoader>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import React, { useState, useContext } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {
|
||||
Text,
|
||||
PageSection,
|
||||
TextContent,
|
||||
Divider,
|
||||
Wizard,
|
||||
AlertVariant,
|
||||
WizardFooter,
|
||||
|
@ -20,6 +17,7 @@ import { CapabilityConfig } from "./CapabilityConfig";
|
|||
import { ClientRepresentation } from "../models/client-model";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { RealmContext } from "../../components/realm-context/RealmContext";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
|
||||
export const NewClientForm = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
|
@ -96,12 +94,10 @@ export const NewClientForm = () => {
|
|||
return (
|
||||
<>
|
||||
<Alerts />
|
||||
<PageSection variant="light">
|
||||
<TextContent>
|
||||
<Text component="h1">{title}</Text>
|
||||
</TextContent>
|
||||
</PageSection>
|
||||
<Divider />
|
||||
<ViewHeader
|
||||
titleKey="clients:createClient"
|
||||
subKey="clients:clientsExplain"
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<Wizard
|
||||
onClose={() => history.push("/clients")}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import React, { useContext } from "react";
|
||||
import {
|
||||
PageSection,
|
||||
Text,
|
||||
TextContent,
|
||||
Divider,
|
||||
Form,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
|
@ -20,6 +17,7 @@ import { HttpClientContext } from "../../http-service/HttpClientContext";
|
|||
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { RealmContext } from "../../components/realm-context/RealmContext";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
|
||||
export const ImportForm = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
|
@ -55,13 +53,10 @@ export const ImportForm = () => {
|
|||
return (
|
||||
<>
|
||||
<Alerts />
|
||||
<PageSection variant="light">
|
||||
<TextContent>
|
||||
<Text component="h1">{t("importClient")}</Text>
|
||||
{t("clientsExplain")}
|
||||
</TextContent>
|
||||
</PageSection>
|
||||
<Divider />
|
||||
<ViewHeader
|
||||
titleKey="clients:importClient"
|
||||
subKey="clients:clientsExplain"
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<Form isHorizontal onSubmit={handleSubmit(save)}>
|
||||
<JsonFileUpload id="realm-file" onChange={handleFileChange} />
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
"clearFile": "Clear this file",
|
||||
"on": "On",
|
||||
"off":"Off",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
|
||||
"signOut": "Sign out",
|
||||
"manageAccount": "Manage account",
|
||||
|
|
108
src/components/view-header/ViewHeader.tsx
Normal file
108
src/components/view-header/ViewHeader.tsx
Normal file
|
@ -0,0 +1,108 @@
|
|||
import React, { ReactElement, useContext, useState } from "react";
|
||||
import {
|
||||
Text,
|
||||
PageSection,
|
||||
TextContent,
|
||||
Divider,
|
||||
Level,
|
||||
LevelItem,
|
||||
Switch,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarItem,
|
||||
Badge,
|
||||
Select,
|
||||
} from "@patternfly/react-core";
|
||||
import { HelpContext } from "../help-enabler/HelpHeader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type ViewHeaderProps = {
|
||||
titleKey: string;
|
||||
badge?: string;
|
||||
subKey: string;
|
||||
selectItems?: ReactElement[];
|
||||
isEnabled?: boolean;
|
||||
onSelect?: (value: string) => void;
|
||||
onToggle?: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export const ViewHeader = ({
|
||||
titleKey,
|
||||
badge,
|
||||
subKey,
|
||||
selectItems,
|
||||
isEnabled,
|
||||
onSelect,
|
||||
onToggle,
|
||||
}: ViewHeaderProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { enabled } = useContext(HelpContext);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [checked, setChecked] = useState(isEnabled);
|
||||
return (
|
||||
<>
|
||||
<PageSection variant="light">
|
||||
<Level hasGutter>
|
||||
<LevelItem>
|
||||
<Level>
|
||||
<LevelItem>
|
||||
<TextContent className="pf-u-mr-sm">
|
||||
<Text component="h1">{t(titleKey)}</Text>
|
||||
</TextContent>
|
||||
</LevelItem>
|
||||
{badge && (
|
||||
<LevelItem>
|
||||
<Badge>{badge}</Badge>
|
||||
</LevelItem>
|
||||
)}
|
||||
</Level>
|
||||
</LevelItem>
|
||||
<LevelItem></LevelItem>
|
||||
{selectItems && (
|
||||
<LevelItem>
|
||||
<Toolbar>
|
||||
<ToolbarContent>
|
||||
<ToolbarItem>
|
||||
<Switch
|
||||
id={`${titleKey}-switch`}
|
||||
label={t("common:enabled")}
|
||||
labelOff={t("common:disabled")}
|
||||
className="pf-u-mr-lg"
|
||||
isChecked={checked}
|
||||
onChange={(value) => {
|
||||
if (onToggle) {
|
||||
onToggle(value);
|
||||
}
|
||||
setChecked(value);
|
||||
}}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<Select
|
||||
isOpen={open}
|
||||
onToggle={() => setOpen(!open)}
|
||||
onSelect={(_, value) => {
|
||||
if (onSelect) {
|
||||
onSelect(value as string);
|
||||
}
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{selectItems}
|
||||
</Select>
|
||||
</ToolbarItem>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
</LevelItem>
|
||||
)}
|
||||
</Level>
|
||||
{enabled && (
|
||||
<TextContent>
|
||||
<Text>{t(subKey)}</Text>
|
||||
</TextContent>
|
||||
)}
|
||||
</PageSection>
|
||||
<Divider />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,16 +1,13 @@
|
|||
import React, { useState, FormEvent, useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Text,
|
||||
PageSection,
|
||||
TextContent,
|
||||
FormGroup,
|
||||
Form,
|
||||
TextInput,
|
||||
Switch,
|
||||
ActionGroup,
|
||||
Button,
|
||||
Divider,
|
||||
AlertVariant,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
|
@ -19,6 +16,7 @@ import { RealmRepresentation } from "../models/Realm";
|
|||
import { HttpClientContext } from "../../http-service/HttpClientContext";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
|
||||
export const NewRealmForm = () => {
|
||||
const { t } = useTranslation("realm");
|
||||
|
@ -50,12 +48,7 @@ export const NewRealmForm = () => {
|
|||
return (
|
||||
<>
|
||||
<Alerts />
|
||||
<PageSection variant="light">
|
||||
<TextContent>
|
||||
<Text component="h1">Create Realm</Text>
|
||||
</TextContent>
|
||||
</PageSection>
|
||||
<Divider />
|
||||
<ViewHeader titleKey="realm:createRealm" subKey="realm:realmExplain" />
|
||||
<PageSection variant="light">
|
||||
<Form isHorizontal onSubmit={handleSubmit(save)}>
|
||||
<JsonFileUpload id="kc-realm-filename" onChange={handleFileChange} />
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"enabled":"Enabled",
|
||||
"create":"Create",
|
||||
"createRealm": "Create realm",
|
||||
"realmExplain": "A realm manages a set of users, credentials, roles, and groups. A user belongs to and logs into a realm. Realms are isolated from one another and can only manage and authenticate the users that they control.",
|
||||
"noRealmRoles": "No realm roles",
|
||||
"emptyStateText": "There aren't any realm roles in this realm. Create a realm role to get started."
|
||||
}
|
||||
|
|
39
src/stories/ViewHeader.stories.tsx
Normal file
39
src/stories/ViewHeader.stories.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import React from "react";
|
||||
import { Meta, Story } from "@storybook/react";
|
||||
import { Page, SelectOption } from "@patternfly/react-core";
|
||||
import {
|
||||
ViewHeader,
|
||||
ViewHeaderProps,
|
||||
} from "../components/view-header/ViewHeader";
|
||||
|
||||
export default {
|
||||
title: "View Header",
|
||||
component: ViewHeader,
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<ViewHeaderProps> = (args) => (
|
||||
<Page>
|
||||
<ViewHeader {...args} />
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const Extended = Template.bind({});
|
||||
Extended.args = {
|
||||
titleKey: "This is the title",
|
||||
badge: "badge",
|
||||
subKey: "This is the description.",
|
||||
selectItems: [
|
||||
<SelectOption key="first" value="first-item">
|
||||
First item
|
||||
</SelectOption>,
|
||||
<SelectOption key="second" value="second-item">
|
||||
Second item
|
||||
</SelectOption>,
|
||||
],
|
||||
};
|
||||
|
||||
export const Simple = Template.bind({});
|
||||
Simple.args = {
|
||||
titleKey: "Title simple",
|
||||
subKey: "Some lengthy description about what this is about.",
|
||||
};
|
Loading…
Reference in a new issue