Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Field] Create new Field components #477

Merged
merged 108 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
72ef9e1
[Field] Create new Field components
atomiks Jul 15, 2024
f6d6082
Remove directories
atomiks Jul 15, 2024
3648440
Update docs
atomiks Jul 15, 2024
bf8a110
Add FieldControl
atomiks Jul 15, 2024
6b6b549
Fix label docs
atomiks Jul 15, 2024
1a3bd78
Update docs
atomiks Jul 15, 2024
2a162b0
Fix docs linting
atomiks Jul 15, 2024
5e05065
Remove global Label, update API
atomiks Jul 16, 2024
adfc0fa
Fix FieldValidity docgen
atomiks Jul 16, 2024
4fe5655
Fix FieldMessage test
atomiks Jul 16, 2024
3e9e227
Remove react-label
atomiks Jul 16, 2024
9e7371c
Make components Field-aware
atomiks Jul 16, 2024
5f63453
Description -> Message
atomiks Jul 16, 2024
1c802a4
Update
atomiks Jul 16, 2024
19a5219
Restore Name field
atomiks Jul 16, 2024
38e6431
Add Fieldset components
atomiks Jul 16, 2024
bfefc20
Add Fieldset tests
atomiks Jul 16, 2024
f0eb3a4
Fix docs linting
atomiks Jul 16, 2024
87f9498
Rename to Legend
atomiks Jul 16, 2024
0ea4cd0
Update docs imports
atomiks Jul 16, 2024
c7f1eec
Fix type export
atomiks Jul 16, 2024
c2973bd
Update docs
atomiks Jul 16, 2024
40a5673
Add disabled prop
atomiks Jul 17, 2024
cc3199d
Propagate disabled states
atomiks Jul 17, 2024
09185bc
Fix jsdoc
atomiks Jul 17, 2024
2775573
Adjust styles
atomiks Jul 17, 2024
d87a1c5
Adjust early return
atomiks Jul 17, 2024
1576846
Tests
atomiks Jul 17, 2024
331909b
Add use client
atomiks Jul 17, 2024
a8eb9fa
Fix docs
atomiks Jul 17, 2024
2220c24
Linting fixes
atomiks Jul 17, 2024
5412429
Update Fieldset docs
atomiks Jul 17, 2024
d4dfd24
Run formatted
atomiks Jul 17, 2024
b26577b
Add disabled prop test
atomiks Jul 17, 2024
b1eed81
Fix type
atomiks Jul 25, 2024
0177f92
Memo ownerStates
atomiks Jul 25, 2024
8108ff2
Support async show prop
atomiks Jul 25, 2024
055d268
Refactor show evaluation
atomiks Jul 25, 2024
d45e0e6
Add validity style hooks
atomiks Jul 25, 2024
bb1ed6f
Fix async test
atomiks Jul 25, 2024
14a727d
Refactor validate API
atomiks Jul 25, 2024
ca28bcd
Docs
atomiks Jul 25, 2024
ca13dda
Update docs demo
atomiks Jul 25, 2024
169a754
Handle async and custom messages
atomiks Jul 26, 2024
504f94b
Fix default
atomiks Jul 26, 2024
8a1840b
Add name prop to Field.Root
atomiks Jul 26, 2024
800bab2
Update tests
atomiks Jul 26, 2024
dc4a50e
Fix test
atomiks Jul 26, 2024
bd70fd5
Fix tests
atomiks Jul 26, 2024
73f64b3
Update ValidityState API
atomiks Jul 26, 2024
25422d7
Avoid destructuring
atomiks Jul 26, 2024
5edc3c9
Simplify JSX
atomiks Jul 26, 2024
6b408b4
Ignore ownerState
atomiks Jul 26, 2024
fed7dd0
Simplify docs
atomiks Jul 26, 2024
3f1f7ef
Await only promises
atomiks Jul 26, 2024
af032d4
Remove unused async waits
atomiks Jul 26, 2024
fa0b668
Fix tests
atomiks Jul 26, 2024
6bf3dcb
useFieldControlValidation
atomiks Jul 26, 2024
048efec
Remove middle function
atomiks Jul 26, 2024
e62d3d9
use client
atomiks Jul 26, 2024
d8a9b5f
Docs lint
atomiks Jul 26, 2024
2447e54
Feedback
atomiks Jul 29, 2024
b8a414a
Refactor props
atomiks Aug 1, 2024
407f624
Update Field.Validity doc
atomiks Aug 1, 2024
68bdf18
Fix Switch test
atomiks Aug 1, 2024
cc37e43
Multiple errors
atomiks Aug 1, 2024
70c4705
Fix default
atomiks Aug 1, 2024
ce61e6c
Update doc
atomiks Aug 1, 2024
59c5179
Correct prop name
atomiks Aug 1, 2024
60a9d68
Update FieldMessage test
atomiks Aug 1, 2024
265b3fa
Docs
atomiks Aug 1, 2024
1bd446e
Split Message into Error and Description
atomiks Aug 2, 2024
50f6f0d
Render customError
atomiks Aug 2, 2024
73736b9
Tests
atomiks Aug 4, 2024
16154a4
Update types
atomiks Aug 4, 2024
a741889
Add context displayNames
atomiks Aug 4, 2024
0b87e9f
Fix NumberField, handle custom label rendering
atomiks Aug 5, 2024
3b5c722
Use explicit label id
atomiks Aug 5, 2024
d611feb
API revamp
atomiks Aug 6, 2024
b36ae7c
Merge
atomiks Aug 6, 2024
6f5fdc8
Fix tests
atomiks Aug 6, 2024
e9ae9a8
Component integration / tests
atomiks Aug 6, 2024
8ed4eb3
Fix types
atomiks Aug 6, 2024
f03b7ad
Fix SwitchThumb context test
atomiks Aug 6, 2024
a438316
Fix component integration
atomiks Aug 6, 2024
2cdf44b
Move commitValidation
atomiks Aug 7, 2024
436ff1c
Remove inheritComponent
atomiks Aug 7, 2024
8f99aee
Use || for disabled components
atomiks Aug 7, 2024
c36f279
Remove onBlur temporarily
atomiks Aug 7, 2024
98fdb6a
Update Slider impl
atomiks Aug 7, 2024
3c87190
Fix import
atomiks Aug 7, 2024
d997e32
Fix docs lint
atomiks Aug 7, 2024
95d6bf4
Remove range
atomiks Aug 7, 2024
68bd4e2
Update server handling
atomiks Aug 8, 2024
3e2f881
Update Validity types
atomiks Aug 8, 2024
bfa5bc7
Update
atomiks Aug 9, 2024
fbecae2
Add markedDirty state
atomiks Aug 9, 2024
cde6971
Change forceShow logic
atomiks Aug 9, 2024
a23d0aa
Fix lint
atomiks Aug 9, 2024
176f63a
Fix lint
atomiks Aug 9, 2024
88b7c51
Remove unnecessary setter call
atomiks Aug 9, 2024
1e6314a
Update Async demo
atomiks Aug 9, 2024
1bc2bea
Use || for disabled fieldset check
atomiks Aug 9, 2024
8001c30
Fix validity style hooks
atomiks Aug 9, 2024
0726553
Make FieldError a div
atomiks Aug 9, 2024
fff10a2
Update docs
atomiks Aug 9, 2024
656d573
Render div
atomiks Aug 9, 2024
2d7ede7
Lint
atomiks Aug 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions docs/data/base/components/field/UnstyledFieldAsync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import * as React from 'react';
import * as Field from '@base_ui/react/Field';
import { styled } from '@mui/system';

const cache = new Map();

function checkAvailability(name) {
const takenNames = ['admin', 'root', 'superuser'];
return new Promise((resolve) => {
setTimeout(() => {
const result = takenNames.includes(name) ? 'Name taken' : null;
cache.set(name, result);
resolve(result);
}, 500);
});
}

export default function UnstyledFieldAsync() {
const [loading, setLoading] = React.useState(false);

async function handleValidate(value) {
const name = value;

if (name === '') {
return null;
}

const isCached = cache.has(name);
if (isCached) {
return cache.get(name);
}

setLoading(true);

try {
const error = await checkAvailability(name);
setLoading(false);
return error;
} catch (e) {
setLoading(false);
return 'Failed to fetch name availability';
}
}

return (
<div>
<h3>Handle availability checker</h3>
<FieldRoot
validate={handleValidate}
validateOnChange
validateDebounceTime={500}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Field.Label>@</Field.Label>
<Field.Validity>
{(state) => (
<FieldControl
data-pending={state.value === '' || loading || undefined}
/>
)}
</Field.Validity>
</div>
<Field.Validity>
{(state) => {
if (loading) {
return <FieldDescription>Checking availability...</FieldDescription>;
}

if (state.value === '') {
return <FieldDescription>Enter a name</FieldDescription>;
}

if (!state.validity.customError) {
return (
<FieldDescription
data-type="success"
role="alert"
aria-live="polite"
>
<strong>@{state.value}</strong> is available
</FieldDescription>
);
}

return <FieldError show="customError" />;
}}
</Field.Validity>
</FieldRoot>
</div>
);
}

const FieldRoot = styled(Field.Root)`
width: 275px;
`;

const FieldControl = styled(Field.Control)`
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
padding: 6px;
font-size: 100%;

&[data-field='invalid']:not([data-pending]) {
border-color: red;
background-color: rgb(255 0 0 / 0.1);
}

&[data-field='valid']:not([data-pending]) {
border-color: green;
background-color: rgb(0 255 0 / 0.1);
}

&:focus {
outline: 0;
border-color: #0078d4;
box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);

&[data-field='invalid']:not([data-pending]) {
border-color: red;
box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
}

&[data-field='valid']:not([data-pending]) {
box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3);
}
}
`;

const FieldDescription = styled(Field.Description)`
font-size: 90%;
margin: 0;
margin-top: 4px;
line-height: 1.1;
color: grey;

&[data-type='success'] {
color: green;
}
`;

const FieldError = styled(Field.Error)`
display: block;
font-size: 90%;
margin: 0;
margin-top: 4px;
line-height: 1.1;
color: red;
`;
149 changes: 149 additions & 0 deletions docs/data/base/components/field/UnstyledFieldAsync.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import * as React from 'react';
import * as Field from '@base_ui/react/Field';
import { styled } from '@mui/system';

const cache = new Map<string, string | null>();

function checkAvailability(name: string) {
const takenNames = ['admin', 'root', 'superuser'];
return new Promise<string | null>((resolve) => {
setTimeout(() => {
const result = takenNames.includes(name) ? 'Name taken' : null;
cache.set(name, result);
resolve(result);
}, 500);
});
}

export default function UnstyledFieldAsync() {
const [loading, setLoading] = React.useState(false);

async function handleValidate(value: unknown) {
const name = value as string;

if (name === '') {
return null;
}

const isCached = cache.has(name);
if (isCached) {
return cache.get(name) as string | null;
}

setLoading(true);

try {
const error = await checkAvailability(name);
setLoading(false);
return error;
} catch (e) {
setLoading(false);
return 'Failed to fetch name availability';
}
}

return (
<div>
<h3>Handle availability checker</h3>
<FieldRoot
validate={handleValidate}
validateOnChange
validateDebounceTime={500}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Field.Label>@</Field.Label>
<Field.Validity>
{(state) => (
<FieldControl
data-pending={state.value === '' || loading || undefined}
/>
)}
</Field.Validity>
</div>
<Field.Validity>
{(state) => {
if (loading) {
return <FieldDescription>Checking availability...</FieldDescription>;
}

if (state.value === '') {
return <FieldDescription>Enter a name</FieldDescription>;
}

if (!state.validity.customError) {
return (
<FieldDescription
data-type="success"
role="alert"
aria-live="polite"
>
<strong>@{state.value as string}</strong> is available
</FieldDescription>
);
}

return <FieldError show="customError" />;
}}
</Field.Validity>
</FieldRoot>
</div>
);
}

const FieldRoot = styled(Field.Root)`
width: 275px;
`;

const FieldControl = styled(Field.Control)`
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
padding: 6px;
font-size: 100%;

&[data-field='invalid']:not([data-pending]) {
border-color: red;
background-color: rgb(255 0 0 / 0.1);
}

&[data-field='valid']:not([data-pending]) {
border-color: green;
background-color: rgb(0 255 0 / 0.1);
}

&:focus {
outline: 0;
border-color: #0078d4;
box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);

&[data-field='invalid']:not([data-pending]) {
border-color: red;
box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
}

&[data-field='valid']:not([data-pending]) {
box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3);
}
}
`;

const FieldDescription = styled(Field.Description)`
font-size: 90%;
margin: 0;
margin-top: 4px;
line-height: 1.1;
color: grey;

&[data-type='success'] {
color: green;
}
`;

const FieldError = styled(Field.Error)`
display: block;
font-size: 90%;
margin: 0;
margin-top: 4px;
line-height: 1.1;
color: red;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as React from 'react';
import * as Field from '@base_ui/react/Field';
import { styled } from '@mui/system';

export default function UnstyledFieldIntroduction() {
return (
<FieldRoot validate={(value) => (value === 'admin' ? 'Name not allowed' : null)}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Field.Label>Name</Field.Label>
<FieldControl required pattern="[a-zA-Z0-9]+" />
</div>
<Field.Validity>
{({ validity, value }) => {
if (
validity.valueMissing ||
validity.patternMismatch ||
value === 'admin'
) {
return null;
}

return (
<FieldDescription>
Your name will be visible on your profile.
</FieldDescription>
);
}}
</Field.Validity>
<FieldError show="customError" />
<FieldError show="valueMissing" />
<FieldError show="patternMismatch">
Only alphanumeric characters are allowed (a-z, A-Z, 0-9).
</FieldError>
</FieldRoot>
);
}

const FieldRoot = styled(Field.Root)`
width: 275px;
`;

const FieldControl = styled(Field.Control)`
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
padding: 6px;
font-size: 100%;

&[data-field='invalid'] {
border-color: red;
background-color: rgb(255 0 0 / 0.1);
}

&:focus {
outline: 0;
border-color: #0078d4;
box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);

&[data-field='invalid'] {
border-color: red;
box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
}
}
`;

const FieldDescription = styled(Field.Description)`
font-size: 90%;
margin-bottom: 0;
margin-top: 4px;
line-height: 1.1;
color: grey;

&[data-error] {
color: red;
}
`;

const FieldError = styled(Field.Error)`
display: block;
font-size: 90%;
margin: 0;
margin-bottom: 0;
margin-top: 4px;
line-height: 1.1;
color: red;
`;
Loading
Loading