feat: create layaut module components

This commit is contained in:
Diana Olarte
2025-09-24 22:59:34 +10:00
committed by Adolfo R. Brandes
parent 80d04ed000
commit b825a8bdd9
4 changed files with 145 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
import { ReactNode } from 'react';
import { StudioHeader } from '@edx/frontend-component-header';
import AuthZTitle, { AuthZTitleProps } from './AuthZTitle';
interface AuthZLayoutProps extends AuthZTitleProps {
children: ReactNode;
context: {
id: string;
org: string;
title: string;
}
}
const AuthZLayout = ({ children, context, ...props }: AuthZLayoutProps) => (
<>
<StudioHeader
number={context.id}
org={context.org}
title={context.title}
/>
<AuthZTitle {...props} />
{children}
</>
);
export default AuthZLayout;

View File

@@ -0,0 +1,62 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import AuthZTitle, { AuthZTitleProps } from './AuthZTitle';
describe('AuthZTitle', () => {
const defaultProps: AuthZTitleProps = {
activeLabel: 'Current Page',
pageTitle: 'Page Title',
pageSubtitle: 'Page Subtitle',
};
it('renders without optional fields', () => {
render(<AuthZTitle {...defaultProps} />);
expect(screen.getByText(defaultProps.activeLabel)).toBeInTheDocument();
expect(screen.getByText(defaultProps.pageTitle)).toBeInTheDocument();
expect(screen.getByText(defaultProps.pageSubtitle as string)).toBeInTheDocument();
});
it('renders breadcrumb with links and active label', () => {
const navLinks = [
{ label: 'Root', to: '/' },
{ label: 'Section', to: '/section' },
];
render(<AuthZTitle {...defaultProps} navLinks={navLinks} />);
navLinks.forEach(({ label }) => {
expect(screen.getByText(label)).toBeInTheDocument();
});
expect(screen.getByText(defaultProps.activeLabel)).toBeInTheDocument();
});
it('renders page title', () => {
render(<AuthZTitle {...defaultProps} />);
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(defaultProps.pageTitle);
});
it('renders page subtitle as ReactNode', () => {
const subtitleNode = <div data-testid="custom-subtitle">Custom Subtitle</div>;
render(<AuthZTitle {...defaultProps} pageSubtitle={subtitleNode} />);
expect(screen.getByTestId('custom-subtitle')).toBeInTheDocument();
});
it('renders action buttons and triggers onClick', () => {
const onClick1 = jest.fn();
const onClick2 = jest.fn();
const actions = [
{ label: 'Save', onClick: onClick1 },
{ label: 'Cancel', onClick: onClick2 },
];
render(<AuthZTitle {...defaultProps} actions={actions} />);
actions.forEach(({ label, onClick }) => {
const button = screen.getByRole('button', { name: label });
expect(button).toBeInTheDocument();
fireEvent.click(button);
expect(onClick).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,50 @@
import { ReactNode } from 'react';
import {
Breadcrumb, Col, Container, Row, Button, Badge,
} from '@openedx/paragon';
interface BreadcrumbLink {
label: string;
to?: string;
}
interface Action {
label: string;
onClick: () => void;
}
export interface AuthZTitleProps {
activeLabel: string;
pageTitle: string;
pageSubtitle: string | ReactNode;
navLinks?: BreadcrumbLink[];
actions?: Action[];
}
const AuthZTitle = ({
activeLabel, navLinks = [], pageTitle, pageSubtitle, actions = [],
}: AuthZTitleProps) => (
<Container className="p-5 bg-light-100">
<Breadcrumb
links={navLinks}
activeLabel={activeLabel}
/>
<Row className="mt-4">
<Col xs={12} md={8} className="mb-4">
<h1 className="text-primary">{pageTitle}</h1>
{typeof pageSubtitle === 'string'
? <h3><Badge className="py-2 px-3 font-weight-normal" variant="light">{pageSubtitle}</Badge></h3>
: pageSubtitle}
</Col>
<Col xs={12} md={4}>
<div className="d-flex justify-content-md-end">
{
actions.map(({ label, onClick }) => <Button key={`authz-header-action-${label}`} onClick={onClick}>{label}</Button>)
}
</div>
</Col>
</Row>
</Container>
);
export default AuthZTitle;

View File

@@ -0,0 +1,6 @@
.authz-libraries {
.pgn__breadcrumb li:first-child a {
color: var(--pgn-color-breadcrumb-active);
text-decoration: none;
}
}