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 {
|
||||
display: grid;
|
||||
grid-template-areas: "input label";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
@layer ui.input {
|
||||
@layer ui.layout {
|
||||
.interactive {
|
||||
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 */
|
||||
import { useState } from '../../lib/teact/teact';
|
||||
import { useState } from '../../../lib/teact/teact';
|
||||
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import Control, {
|
||||
ControlAfter,
|
||||
ControlBefore,
|
||||
ControlDescription,
|
||||
ControlLabel,
|
||||
} from '../gili/layout/Control';
|
||||
import Interactive from '../gili/layout/Interactive';
|
||||
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';
|
||||
} from '../../gili/layout/Control';
|
||||
import Interactive from '../../gili/layout/Interactive';
|
||||
import Island, {
|
||||
IslandDescription,
|
||||
IslandText,
|
||||
} from '../../gili/layout/Island';
|
||||
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 (
|
||||
<div style="margin-bottom: 2rem; break-inside: avoid">
|
||||
<h3 style="margin: 0 0 0.5rem; font-size: 0.875rem; color: #888; text-transform: uppercase; letter-spacing: 0.05em">
|
||||
{title}
|
||||
</h3>
|
||||
<div
|
||||
style={buildStyle(
|
||||
!noBorder && 'border: 1px solid var(--color-borders-input); border-radius: 0.75rem',
|
||||
'overflow: hidden',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div className={styles.section}>
|
||||
<Island>
|
||||
<h3 className={styles.sectionTitle}>{title}</h3>
|
||||
<div className={buildClassName(styles.sectionContent, noBorder && styles.sectionContentNoBorder)}>
|
||||
{children}
|
||||
</div>
|
||||
</Island>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const FieldTest = () => {
|
||||
const FieldDemo = () => {
|
||||
const [check1, setCheck1] = useState(false);
|
||||
const [check2, setCheck2] = useState(true);
|
||||
const [check3, setCheck3] = useState(false);
|
||||
@ -65,13 +73,50 @@ const FieldTest = () => {
|
||||
const [switch4, setSwitch4] = useState(true);
|
||||
|
||||
return (
|
||||
<div style="overflow-y: auto; height: 100vh">
|
||||
<div style="columns: 28rem 2; column-gap: 2rem; padding: 2rem">
|
||||
<h2 style="margin: 0 0 1.5rem; column-span: all">Control Component Test</h2>
|
||||
<Surface className={styles.root} scrollable>
|
||||
<div className={styles.content}>
|
||||
<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 */}
|
||||
<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={check2} onChange={setCheck2} />
|
||||
<Checkbox checked={false} isInvalid onChange={setCheck1} />
|
||||
@ -481,7 +526,7 @@ const FieldTest = () => {
|
||||
|
||||
{/* Control without Interactive */}
|
||||
<Section noBorder title="Control without Interactive (no padding/hover)">
|
||||
<div style="padding: 0.5rem 1rem">
|
||||
<div className={styles.bareControl}>
|
||||
<Control>
|
||||
<Checkbox checked={check2} onChange={setCheck2} />
|
||||
<ControlLabel>Bare field, custom container</ControlLabel>
|
||||
@ -494,8 +539,8 @@ const FieldTest = () => {
|
||||
</div>
|
||||
</Section>
|
||||
</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 { formatDateTime, formatMessageListDate } from '../../util/localization/dateFormat';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatDateTime, formatMessageListDate } from '../../../util/localization/dateFormat';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import styles from './TestDateFormat.module.scss';
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
<style>
|
||||
@layer reset, variables, ui, components;
|
||||
@layer ui {
|
||||
@layer tablist, spinner, button, input;
|
||||
@layer tablist, spinner, button, input, layout;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user