[web] Create an initial implementation for the CIAM page layout (#29373)

* Add a Storybook Layout page compiling all the "small pages" layouts

* Add a CIAM page layout to Storybook and create an initial Layout

* Use rem in font mixins

* Add a `--ciam-` prefix to the new CSS variables

* Fix linting

GitOrigin-RevId: 7a89fd1531c87597a918a9170d174cce556d77c4
This commit is contained in:
Antoine Clausse
2025-10-29 11:18:15 +01:00
committed by Copybot
parent cb0cfcfd82
commit 698a6013de
6 changed files with 578 additions and 0 deletions

View File

@@ -0,0 +1,297 @@
import _ from 'lodash'
import { ComponentType } from 'react'
import { Navbar } from 'react-bootstrap'
import OLPageContentCard from '@/shared/components/ol/ol-page-content-card'
import OLRow from '@/shared/components/ol/ol-row'
import OLCol from '@/shared/components/ol/ol-col'
import OLButton from '@/shared/components/ol/ol-button'
import overleafLogo from '@/shared/svgs/overleaf-a-ds-solution-mallard.svg'
const lorem = (n: number) => {
const quacks = ['quack', 'quack', 'quack', 'quak']
let result = ''
if (n >= 1) result += 'Lorem'
if (n >= 2) result += ' epsom'
for (let i = 2; i < n; i++) {
const next =
result.at(-1) === '.'
? ' ' + _.capitalize(quacks[Math.floor(Math.random() * quacks.length)])
: quacks[Math.floor(Math.random() * (quacks.length + 1))]
result += next ? ' ' + next : '.'
}
if (result.at(-1) !== '.') result += '.'
return result
}
const Nav = () => <Navbar className="navbar-default navbar-main" />
export const UnsuportedBrowser = () => (
<main className="content content-alt full-height" id="main-content">
<div className="container full-height">
<div className="error-container full-height">
<div className="error-details">
<h1 className="error-status">Unsupported Browser</h1>
<p className="error-description">{lorem(60)}</p>
<hr />
<p>{lorem(40)}</p>
</div>
</div>
</div>
</main>
)
export const Error400 = () => (
<main className="content content-alt full-height" id="main-content">
<div className="container full-height">
<div className="error-container full-height">
<div className="error-details">
<p className="error-status">Something went wrong, sorry.</p>
<p className="error-description">{lorem(15)}</p>
</div>
</div>
</div>
</main>
)
export const Error404 = () => (
<>
<Nav />
<main className="content content-alt" id="main-content">
<div className="container">
<div className="error-container">
<div className="error-details">
<p className="error-status">Not found</p>
<p className="error-description">{lorem(20)}</p>
</div>
</div>
</div>
</main>
</>
)
export const Closed = () => (
<>
<Nav />
<main className="content" id="main-content">
<div className="container">
<div className="row">
<div className="col-lg-8 col-lg-offset-2 text-center">
<div className="page-header">
<h1>Maintenance</h1>
</div>
<p>{lorem(6)}</p>
</div>
</div>
</div>
</main>
</>
)
export const PlannedMaintenance = () => (
<>
<Nav />
<main className="content" id="main-content">
<div className="container">
<div className="row">
<div className="col-lg-8 col-lg-offset-2">
<div className="page-header">
<h1>Planned Maintenance</h1>
</div>
<p>{lorem(6)}</p>
</div>
</div>
</div>
</main>
</>
)
export const PostGateway = () => (
<>
<div className="content content-alt">
<div className="container">
<div className="row">
<div className="col-lg-6 offset-lg-3">
<div className="card">
<div className="card-body">
<p className="text-center">
Please wait while we process your request.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
export const AccountSuspended = () => (
<main className="content content-alt" id="main-content">
<div className="container-custom-sm mx-auto">
<div className="card">
<div className="card-body">
<h3>Your account is suspended</h3>
<p>{lorem(6)}</p>
</div>
</div>
</div>
</main>
)
export const Restricted = () => (
<>
<Nav />
<main className="content" id="main-content">
<div className="container">
<div className="row">
<div className="col-md-8 offset-md-2 text-center">
<div className="page-header">
<h2>
Restricted, sorry you dont have permission to load this page.
</h2>
</div>
<p>{lorem(23)}</p>
</div>
</div>
</div>
</main>
</>
)
export const OneTimeLogin = () => (
<>
<Nav />
<main className="content content-alt" id="main-content">
<div className="container">
<div className="row">
<div className="col-lg-6 offset-lg-3 col-xl-4 offset-xl-4">
<div className="card">
<div className="card-body">
<div className="page-header">
<h1>We're back!</h1>
</div>
<p>Overleaf is now running normally.</p>
</div>
</div>
</div>
</div>
</div>
</main>
</>
)
export const Invite = () => (
<main className="content content-alt" id="invite-root">
<OLRow className="row row-spaced">
<OLCol lg={{ span: 8, offset: 2 }}>
<OLPageContentCard>
<div className="page-header">
<h1 className="text-center">
<span className="team-invite-name">
max.mustermann@example.com
</span>{' '}
has invited you to join a group subscription on Overleaf
</h1>
</div>
<p className="text-center">{lorem(20)}</p>
</OLPageContentCard>
</OLCol>
</OLRow>
</main>
)
export const NotValid = () => (
<>
<Nav />
<main className="content content-alt" id="main-content">
<div className="container">
<div className="row">
<div className="col-md-8 col-md-offset-2 offset-md-2">
<div className="card project-invite-invalid">
<div className="card-body">
<div className="page-header text-center">
<h1>Invite not valid</h1>
</div>
<div className="row text-center">
<div className="col-12 col-md-12">
<p>{lorem(20)}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</>
)
export const CompleteRegistration = () => (
<>
<Nav />
<main className="content content-alt" id="main-content">
<div className="container">
<div className="row">
<div className="col-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 offset-md-1 offset-lg-2">
<div className="card">
<div className="card-body">
<div className="page-header">
<h1 className="text-center">Dropbox Sync</h1>
</div>
<p>{lorem(20)}</p>
</div>
</div>
</div>
</div>
</div>
</main>
</>
)
export const Ciam = () => (
<div className="ciam-layout">
<a
href="/"
aria-label="Overleaf"
className="brand"
style={{ backgroundImage: `url("${overleafLogo}")` }}
/>
<div className="ciam-container">
<main className="ciam-card" id="main-content">
<h1>Create your Overleaf account</h1>
<p>{lorem(20)}</p>
<hr />
<p>{lorem(20)}</p>
<OLButton>Button</OLButton>
</main>
</div>
<footer>
<a href="https://www.overleaf.com/legal#Privacy">Privacy</a>
<a href="https://www.overleaf.com/legal#Terms">Terms</a>
</footer>
</div>
)
export default {
title: 'Shared / Layouts',
args: {
label: 'Option',
},
parameters: {
layout: 'fullscreen', // This is crucial for vh/vw layouts
},
decorators: [
(Story: ComponentType) => (
<div style={{ height: '100vh', width: '100vw' }}>
<style>
{`.content {
min-height: 100vh;
padding-top: 93px;
}`}
</style>
<Story />
</div>
),
],
}

View File

@@ -1,4 +1,5 @@
@import 'account-settings';
@import 'ciam';
@import 'cms';
@import 'content';
@import 'project-list';

View File

@@ -0,0 +1,68 @@
.ciam-layout {
--ciam-color-neutral-50: #fafafa;
--ciam-color-neutral-100: #f2f2f2;
--ciam-color-neutral-200: #e6e6e6;
--ciam-color-neutral-300: #d6d6d6;
--ciam-color-neutral-400: #c7c7c7;
--ciam-color-neutral-500: #b5b5b5;
--ciam-color-neutral-600: #a1a1a1;
--ciam-color-neutral-700: #8a8a8a;
--ciam-color-neutral-800: #6b6b6b;
--ciam-color-neutral-900: #383838;
--ciam-color-neutral-950: #262626;
--ciam-color-green-50: #f2f8f5;
--ciam-color-green-100: #e3f2eb;
--ciam-color-green-200: #c0e7d6;
--ciam-color-green-300: #8adbb7;
--ciam-color-green-400: #38cc89;
--ciam-color-green-500: #26b072;
--ciam-color-green-600: #158954;
--ciam-color-green-700: #19754c;
--ciam-color-green-800: #196241;
--ciam-color-green-900: #164630;
--ciam-color-green-950: #112c20;
--ciam-color-yellow-50: #fffaeb;
--ciam-color-yellow-100: #fff7db;
--ciam-color-yellow-200: #ffeeb8;
--ciam-color-yellow-300: #ffe58f;
--ciam-color-yellow-400: #ffda61;
--ciam-color-yellow-500: #ffcc20;
--ciam-color-yellow-600: #f0b800;
--ciam-color-yellow-700: #d1a000;
--ciam-color-yellow-800: #ad8500;
--ciam-color-yellow-900: #806200;
--ciam-color-yellow-950: #574200;
--ciam-color-red-50: #fff5f7;
--ciam-color-red-100: #fee7eb;
--ciam-color-red-200: #fdc9d3;
--ciam-color-red-300: #fba7b7;
--ciam-color-red-400: #f97b92;
--ciam-color-red-500: #f51d43;
--ciam-color-red-600: #e60a32;
--ciam-color-red-700: #c3092b;
--ciam-color-red-800: #a10723;
--ciam-color-red-900: #75051a;
--ciam-color-red-950: #530412;
--ciam-color-blue-50: #f7fafd;
--ciam-color-blue-100: #ecf2f9;
--ciam-color-blue-200: #d4e3f2;
--ciam-color-blue-300: #bdd4ea;
--ciam-color-blue-400: #a2c3e2;
--ciam-color-blue-500: #7facd7;
--ciam-color-blue-600: #5893cb;
--ciam-color-blue-700: #3470a8;
--ciam-color-blue-800: #2b5d8c;
--ciam-color-blue-900: #1e4161;
--ciam-color-blue-950: #17314a;
--ciam-color-teal-50: #f1f8f8;
--ciam-color-teal-100: #e3f2f1;
--ciam-color-teal-200: #c1e2df;
--ciam-color-teal-300: #9ed1cd;
--ciam-color-teal-400: #6dbab4;
--ciam-color-teal-500: #4a9d96;
--ciam-color-teal-600: #438e88;
--ciam-color-teal-700: #3b7d77;
--ciam-color-teal-800: #2f6460;
--ciam-color-teal-900: #244c49;
--ciam-color-teal-950: #193432;
}

View File

@@ -0,0 +1,107 @@
@mixin ciam-body-xs-regular() {
font-size: 0.75rem;
font-weight: 400;
line-height: 1.25rem;
}
@mixin ciam-body-xs-semibold() {
font-size: 0.75rem;
font-weight: 600;
line-height: 1.25rem;
}
@mixin ciam-body-sm-regular() {
font-size: 0.875rem;
font-weight: 400;
line-height: 1.25rem;
}
@mixin ciam-body-sm-semibold() {
font-size: 0.875rem;
font-weight: 600;
line-height: 1.25rem;
}
@mixin ciam-body-md-regular() {
font-size: 1rem;
font-weight: 400;
line-height: 1.5rem;
}
@mixin ciam-body-md-semibold() {
font-size: 1rem;
font-weight: 600;
line-height: 1.5rem;
}
@mixin ciam-body-lg-regular() {
font-size: 1.125rem;
font-weight: 400;
line-height: 1.75rem;
}
@mixin ciam-body-lg-semibold() {
font-size: 1.125rem;
font-weight: 600;
line-height: 1.75rem;
}
@mixin ciam-heading-xs-regular() {
font-size: 1.125rem;
font-weight: 400;
line-height: 1.5rem;
}
@mixin ciam-heading-xs-semibold() {
font-size: 1.125rem;
font-weight: 600;
line-height: 1.5rem;
}
@mixin ciam-heading-sm-regular() {
font-size: 1.25rem;
font-weight: 400;
line-height: 1.75rem;
}
@mixin ciam-heading-sm-semibold() {
font-size: 1.25rem;
font-weight: 600;
line-height: 1.75rem;
}
@mixin ciam-heading-md-regular() {
font-size: 1.5rem;
font-weight: 400;
line-height: 2rem;
}
@mixin ciam-heading-md-semibold() {
font-size: 1.5rem;
font-weight: 600;
line-height: 2rem;
}
@mixin ciam-heading-lg-regular() {
font-size: 2rem;
font-weight: 400;
line-height: 2.5rem;
}
@mixin ciam-heading-lg-semibold() {
font-size: 2rem;
font-weight: 600;
line-height: 2.5rem;
}
@mixin ciam-heading-xl-regular() {
font-size: 2.5rem;
font-weight: 400;
line-height: 3rem;
}
@mixin ciam-heading-xl-semibold() {
font-size: 2.5rem;
font-weight: 600;
line-height: 3rem;
}

View File

@@ -0,0 +1,35 @@
@import 'ciam-mixins';
@import 'ciam-colors';
// TODO: Replace `fuchsia` by the correct colors.
.ciam-layout {
// Spacings
--ciam-spacing-200: 8px;
--ciam-spacing-250: 10px;
--ciam-spacing-350: 12px;
--ciam-spacing-400: 16px;
--ciam-spacing-600: 24px; // TODO: confirm this variable name (couldn't find in design system)
--ciam-spacing-800: 32px; // TODO: confirm this variable name (couldn't find in design system)
--ciam-spacing-1300: 52px;
// Base variables
--ciam-color-text-secondary: var(--ciam-color-neutral-800);
--ciam-color-text-primary: var(--ciam-color-neutral-900);
--ciam-border-radius-200: var(--ciam-spacing-300);
--ciam-border-radius-400: var(--ciam-spacing-400);
--ciam-font-family-sans: 'Inter', sans-serif;
// Links
// used in services/web/frontend/stylesheets/base/links.scss
--link-color: var(--ciam-color-text-secondary);
--link-hover-color: fuchsia;
// TODO: validate that this is correct
--link-visited-color: var(--ciam-color-text-secondary);
--link-color-dark: fuchsia;
--link-hover-color-dark: fuchsia;
--link-visited-color-dark: fuchsia;
--link-text-decoration: underline;
--link-hover-text-decoration: none;
}

View File

@@ -0,0 +1,70 @@
@import 'ciam-variables';
@import 'ciam-mixins';
.ciam-layout {
padding: var(--ciam-spacing-350);
display: flex;
min-height: 100%;
flex-direction: column;
font-family: var(--ciam-font-family-sans), sans-serif;
color: var(--ciam-color-text-primary);
font-size: var(--ciam-font-size-400);
line-height: 1.5;
@include ciam-body-md-regular;
.ciam-container {
flex: 1 1 auto;
}
.brand {
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
height: 64px;
width: 130px;
margin: var(--ciam-spacing-350) auto;
display: block;
@include media-breakpoint-up(sm) {
margin: var(--ciam-spacing-350) var(--ciam-spacing-800);
}
}
h1 {
@include ciam-heading-sm-semibold;
}
.ciam-card {
box-shadow:
0 4px 6px -4px rgb(0 0 0 / 10%),
0 1px 29px -3px rgb(0 0 0 / 16%);
padding: var(--ciam-spacing-800) var(--ciam-spacing-400);
border-radius: var(--ciam-border-radius-400);
max-width: 460px;
margin: var(--ciam-spacing-400) auto;
@include media-breakpoint-up(sm) {
padding: var(--ciam-spacing-1300);
}
}
footer {
display: flex;
gap: var(--ciam-spacing-600);
text-transform: uppercase;
justify-content: center;
margin: var(--ciam-spacing-350) auto;
@include media-breakpoint-up(sm) {
margin: var(--ciam-spacing-350) var(--ciam-spacing-800);
justify-content: start;
}
a {
text-decoration: none;
@include ciam-body-sm-regular;
}
}
}