UI: Add Island and Surface (#6851)
This commit is contained in:
parent
7e789e6e77
commit
83ed53d86d
@ -1,4 +1,4 @@
|
|||||||
@layer ui.input {
|
@layer ui.layout {
|
||||||
.control {
|
.control {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "input label";
|
grid-template-areas: "input label";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@layer ui.input {
|
@layer ui.layout {
|
||||||
.interactive {
|
.interactive {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|||||||
38
src/components/gili/layout/Island.module.scss
Normal file
38
src/components/gili/layout/Island.module.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@layer ui.layout {
|
||||||
|
.island {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: var(--border-radius-island);
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: 0px 1px 4px 0px #0000000D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: block;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.island + .island {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.island + .description {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description + .island {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/components/gili/layout/Island.tsx
Normal file
46
src/components/gili/layout/Island.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import styles from './Island.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Island = ({ className, children, ...otherProps }: OwnProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName(styles.island, className)}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const IslandDescription = ({ className, children, ...otherProps }: OwnProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName(styles.description, className)}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const IslandText = ({ className, children, ...otherProps }: OwnProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName(styles.text, className)}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Island;
|
||||||
|
export {
|
||||||
|
IslandDescription,
|
||||||
|
IslandText,
|
||||||
|
};
|
||||||
15
src/components/gili/layout/Surface.module.scss
Normal file
15
src/components/gili/layout/Surface.module.scss
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@use '../../../styles/mixins';
|
||||||
|
|
||||||
|
@layer ui.layout {
|
||||||
|
.root {
|
||||||
|
padding-inline: 1rem;
|
||||||
|
background-color: var(--color-background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable {
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
@include mixins.adapt-padding-to-scrollbar(1rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/components/gili/layout/Surface.tsx
Normal file
33
src/components/gili/layout/Surface.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import styles from './Surface.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||||
|
scrollable?: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Surface = ({
|
||||||
|
scrollable,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...otherProps
|
||||||
|
}: OwnProps) => {
|
||||||
|
const isScrollable = Boolean(scrollable);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName(
|
||||||
|
styles.root,
|
||||||
|
isScrollable && 'custom-scroll',
|
||||||
|
isScrollable && styles.scrollable,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Surface;
|
||||||
81
src/components/test/demo/FieldDemo.module.scss
Normal file
81
src/components/test/demo/FieldDemo.module.scss
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
.root {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
columns: 28rem 2;
|
||||||
|
column-gap: 2rem;
|
||||||
|
padding-block: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullWidth {
|
||||||
|
column-span: all;
|
||||||
|
break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layoutPreview,
|
||||||
|
.section {
|
||||||
|
break-inside: avoid;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layoutPreview {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionContent {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--color-borders-input);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionContentNoBorder {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barePrimitives {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewLabel {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewText {
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bareControl {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
@ -1,40 +1,48 @@
|
|||||||
/* eslint-disable @stylistic/max-len */
|
/* eslint-disable @stylistic/max-len */
|
||||||
import { useState } from '../../lib/teact/teact';
|
import { useState } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
import Control, {
|
import Control, {
|
||||||
ControlAfter,
|
ControlAfter,
|
||||||
ControlBefore,
|
ControlBefore,
|
||||||
ControlDescription,
|
ControlDescription,
|
||||||
ControlLabel,
|
ControlLabel,
|
||||||
} from '../gili/layout/Control';
|
} from '../../gili/layout/Control';
|
||||||
import Interactive from '../gili/layout/Interactive';
|
import Interactive from '../../gili/layout/Interactive';
|
||||||
import Checkbox from '../gili/primitives/Checkbox';
|
import Island, {
|
||||||
import Radio from '../gili/primitives/Radio';
|
IslandDescription,
|
||||||
import Switch from '../gili/primitives/Switch';
|
IslandText,
|
||||||
import CheckboxField from '../gili/templates/CheckboxField';
|
} from '../../gili/layout/Island';
|
||||||
import SwitchField from '../gili/templates/SwitchField';
|
import Surface from '../../gili/layout/Surface';
|
||||||
|
import Checkbox from '../../gili/primitives/Checkbox';
|
||||||
|
import Radio from '../../gili/primitives/Radio';
|
||||||
|
import Switch from '../../gili/primitives/Switch';
|
||||||
|
import CheckboxField from '../../gili/templates/CheckboxField';
|
||||||
|
import SwitchField from '../../gili/templates/SwitchField';
|
||||||
|
|
||||||
function Section({ title, children, noBorder }: { title: string; children: any; noBorder?: boolean }) {
|
import styles from './FieldDemo.module.scss';
|
||||||
|
|
||||||
|
type SectionProps = {
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
noBorder?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Section({ title, children, noBorder }: SectionProps) {
|
||||||
return (
|
return (
|
||||||
<div style="margin-bottom: 2rem; break-inside: avoid">
|
<div className={styles.section}>
|
||||||
<h3 style="margin: 0 0 0.5rem; font-size: 0.875rem; color: #888; text-transform: uppercase; letter-spacing: 0.05em">
|
<Island>
|
||||||
{title}
|
<h3 className={styles.sectionTitle}>{title}</h3>
|
||||||
</h3>
|
<div className={buildClassName(styles.sectionContent, noBorder && styles.sectionContentNoBorder)}>
|
||||||
<div
|
{children}
|
||||||
style={buildStyle(
|
</div>
|
||||||
!noBorder && 'border: 1px solid var(--color-borders-input); border-radius: 0.75rem',
|
</Island>
|
||||||
'overflow: hidden',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldTest = () => {
|
const FieldDemo = () => {
|
||||||
const [check1, setCheck1] = useState(false);
|
const [check1, setCheck1] = useState(false);
|
||||||
const [check2, setCheck2] = useState(true);
|
const [check2, setCheck2] = useState(true);
|
||||||
const [check3, setCheck3] = useState(false);
|
const [check3, setCheck3] = useState(false);
|
||||||
@ -65,13 +73,50 @@ const FieldTest = () => {
|
|||||||
const [switch4, setSwitch4] = useState(true);
|
const [switch4, setSwitch4] = useState(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style="overflow-y: auto; height: 100vh">
|
<Surface className={styles.root} scrollable>
|
||||||
<div style="columns: 28rem 2; column-gap: 2rem; padding: 2rem">
|
<div className={styles.content}>
|
||||||
<h2 style="margin: 0 0 1.5rem; column-span: all">Control Component Test</h2>
|
<h2 className={buildClassName(styles.title, styles.fullWidth)}>Control Component Test</h2>
|
||||||
|
|
||||||
|
<div className={buildClassName(styles.layoutPreview, styles.fullWidth)}>
|
||||||
|
<Island>
|
||||||
|
<IslandText>
|
||||||
|
<h3 className={styles.sectionTitle}>Surface + Islands</h3>
|
||||||
|
<div className={styles.previewCard}>
|
||||||
|
<span className={styles.previewLabel}>Island</span>
|
||||||
|
<span className={styles.previewText}>
|
||||||
|
Regular background, island radius, and 0.5rem padding.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</IslandText>
|
||||||
|
</Island>
|
||||||
|
<IslandDescription>
|
||||||
|
IslandDescription stays attached to the island above it, and the next island starts 1rem lower.
|
||||||
|
</IslandDescription>
|
||||||
|
<Island>
|
||||||
|
<IslandText>
|
||||||
|
<div className={styles.previewCard}>
|
||||||
|
<span className={styles.previewLabel}>Island After Description</span>
|
||||||
|
<span className={styles.previewText}>
|
||||||
|
This island verifies the description-to-island spacing rule.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</IslandText>
|
||||||
|
</Island>
|
||||||
|
<Island>
|
||||||
|
<IslandText>
|
||||||
|
<div className={styles.previewCard}>
|
||||||
|
<span className={styles.previewLabel}>Island After Island</span>
|
||||||
|
<span className={styles.previewText}>
|
||||||
|
This island verifies the direct island-to-island 1rem gap.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</IslandText>
|
||||||
|
</Island>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Bare primitives */}
|
{/* Bare primitives */}
|
||||||
<Section title="Bare Primitives (no Control)">
|
<Section title="Bare Primitives (no Control)">
|
||||||
<div style="display: flex; gap: 1rem; padding: 1rem; align-items: center">
|
<div className={styles.barePrimitives}>
|
||||||
<Checkbox checked={check1} onChange={setCheck1} />
|
<Checkbox checked={check1} onChange={setCheck1} />
|
||||||
<Checkbox checked={check2} onChange={setCheck2} />
|
<Checkbox checked={check2} onChange={setCheck2} />
|
||||||
<Checkbox checked={false} isInvalid onChange={setCheck1} />
|
<Checkbox checked={false} isInvalid onChange={setCheck1} />
|
||||||
@ -481,7 +526,7 @@ const FieldTest = () => {
|
|||||||
|
|
||||||
{/* Control without Interactive */}
|
{/* Control without Interactive */}
|
||||||
<Section noBorder title="Control without Interactive (no padding/hover)">
|
<Section noBorder title="Control without Interactive (no padding/hover)">
|
||||||
<div style="padding: 0.5rem 1rem">
|
<div className={styles.bareControl}>
|
||||||
<Control>
|
<Control>
|
||||||
<Checkbox checked={check2} onChange={setCheck2} />
|
<Checkbox checked={check2} onChange={setCheck2} />
|
||||||
<ControlLabel>Bare field, custom container</ControlLabel>
|
<ControlLabel>Bare field, custom container</ControlLabel>
|
||||||
@ -494,8 +539,8 @@ const FieldTest = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Surface>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FieldTest;
|
export default FieldDemo;
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import type { FormatDateTimeOptions } from '../../util/localization/dateFormat';
|
import type { FormatDateTimeOptions } from '../../../util/localization/dateFormat';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatDateTime, formatMessageListDate } from '../../util/localization/dateFormat';
|
import { formatDateTime, formatMessageListDate } from '../../../util/localization/dateFormat';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import styles from './TestDateFormat.module.scss';
|
import styles from './TestDateFormat.module.scss';
|
||||||
|
|
||||||
@ -54,7 +54,7 @@
|
|||||||
<style>
|
<style>
|
||||||
@layer reset, variables, ui, components;
|
@layer reset, variables, ui, components;
|
||||||
@layer ui {
|
@layer ui {
|
||||||
@layer tablist, spinner, button, input;
|
@layer tablist, spinner, button, input, layout;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user