@a-type/ui 6.0.23 → 6.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/arbor/arbor.js +6 -3
  2. package/dist/arbor/arbor.js.map +1 -1
  3. package/dist/components/card/Card.d.ts +4 -1
  4. package/dist/components/card/Card.js +9 -5
  5. package/dist/components/card/Card.js.map +1 -1
  6. package/dist/components/card/Card.module.css +1 -15
  7. package/dist/components/forms/CheckboxField.js +2 -2
  8. package/dist/components/forms/CheckboxField.js.map +1 -1
  9. package/dist/components/forms/EmojiField.js +8 -8
  10. package/dist/components/forms/EmojiField.js.map +1 -1
  11. package/dist/components/forms/Field.d.ts +7 -2
  12. package/dist/components/forms/Field.js +40 -6
  13. package/dist/components/forms/Field.js.map +1 -1
  14. package/dist/components/forms/Field.module.css +17 -6
  15. package/dist/components/forms/FormikForm.d.ts +1 -1
  16. package/dist/components/forms/FormikForm.stories.d.ts +1 -1
  17. package/dist/components/forms/NumberStepperField.js +6 -6
  18. package/dist/components/forms/NumberStepperField.js.map +1 -1
  19. package/dist/components/forms/SwitchField.js +2 -2
  20. package/dist/components/forms/SwitchField.js.map +1 -1
  21. package/dist/components/forms/TextField.js +2 -2
  22. package/dist/components/forms/TextField.js.map +1 -1
  23. package/dist/components/forms/ToggleGroupField.d.ts +1 -1
  24. package/dist/components/forms/ToggleGroupField.js +5 -3
  25. package/dist/components/forms/ToggleGroupField.js.map +1 -1
  26. package/dist/css/arbor.css +21 -2
  27. package/package.json +1 -1
  28. package/src/arbor/arbor.ts +5 -3
  29. package/src/components/card/Card.module.css +1 -15
  30. package/src/components/card/Card.tsx +17 -3
  31. package/src/components/forms/CheckboxField.tsx +15 -19
  32. package/src/components/forms/EmojiField.tsx +35 -46
  33. package/src/components/forms/Field.module.css +17 -6
  34. package/src/components/forms/Field.tsx +92 -10
  35. package/src/components/forms/NumberStepperField.tsx +19 -20
  36. package/src/components/forms/SwitchField.tsx +15 -19
  37. package/src/components/forms/TextField.tsx +6 -18
  38. package/src/components/forms/ToggleGroupField.tsx +23 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-type/ui",
3
- "version": "6.0.23",
3
+ "version": "6.0.25",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "url": "https://github.com/a-type/ui"
@@ -68,7 +68,6 @@ export function presetAtype<
68
68
  minFontSize: '12px',
69
69
  minWeight: 200,
70
70
  maxWeight: 600,
71
- weightStep: 100,
72
71
  baseWeight: 400,
73
72
  lineHeightStep: 0.125,
74
73
  minLineHeight: 1,
@@ -461,9 +460,12 @@ export function presetAtype<
461
460
  preset.bundleMode('bold', {
462
461
  global: {
463
462
  typography: {
464
- minWeight: 300,
463
+ minWeight: 200,
465
464
  maxWeight: 800,
466
- baseWeight: 500,
465
+ baseWeight: 450,
466
+ },
467
+ shape: {
468
+ lineWidth: 2,
467
469
  },
468
470
  },
469
471
  });
@@ -41,9 +41,6 @@
41
41
  padding: var(--m-space-md);
42
42
  padding-bottom: var(--m-space-xs);
43
43
  gap: var(--m-space-sm);
44
- &[data-compact='true'] {
45
- padding-bottom: 0;
46
- }
47
44
 
48
45
  transition: all var(--m-duration) var(--m-easing);
49
46
 
@@ -83,13 +80,7 @@
83
80
  z-index: 1;
84
81
  padding: var(--m-space-xs) var(--m-space-md);
85
82
 
86
- font-size: var(--m-prose-primary-size);
87
- line-height: var(--m-prose-primary-lineHeight);
88
- font-weight: var(--m-prose-primary-weight);
89
-
90
- [data-compact] & {
91
- font-size: var(--m-prose-ambient-size);
92
- }
83
+ @apply --mx-prose-primary;
93
84
 
94
85
  text-overflow: ellipsis;
95
86
  white-space: nowrap;
@@ -107,11 +98,6 @@
107
98
  line-height: 1;
108
99
  font-weight: var(--m-prose-ambient-weight);
109
100
 
110
- [data-compact] & {
111
- padding: 0 var(--m-space-xs);
112
- margin: 0 var(--m-space-sm);
113
- }
114
-
115
101
  &[data-unstyled] {
116
102
  background-color: unset;
117
103
  padding: 0;
@@ -15,9 +15,12 @@ import cls from './Card.module.css';
15
15
 
16
16
  export const CardRoot = withClassName(withProps(Box, { col: true }), cls.root);
17
17
 
18
+ export type CardSize = 'sm' | 'md' | 'lg';
19
+
18
20
  export function CardMain({
19
21
  className,
20
22
  compact,
23
+ size = compact ? 'sm' : 'md',
21
24
  nonInteractive,
22
25
  ref,
23
26
  style,
@@ -36,7 +39,9 @@ export function CardMain({
36
39
  className?: string;
37
40
  onClick?: (ev: MouseEvent) => void;
38
41
  children?: ReactNode;
42
+ /** @deprecated use size=sm */
39
43
  compact?: boolean;
44
+ size?: CardSize;
40
45
  /** forces non-interactive version */
41
46
  nonInteractive?: boolean;
42
47
  visuallyFocused?: boolean;
@@ -48,7 +53,15 @@ export function CardMain({
48
53
 
49
54
  const rootProps = {
50
55
  ...rest,
51
- className: classNames(cls.main, className),
56
+ className: classNames(
57
+ cls.main,
58
+ {
59
+ '@mode-normal': size === 'lg',
60
+ '@mode-dense': size === 'md',
61
+ '@mode-denser': size === 'sm',
62
+ },
63
+ className,
64
+ ),
52
65
  style,
53
66
  children,
54
67
  };
@@ -60,9 +73,9 @@ export function CardMain({
60
73
  render,
61
74
  state: {
62
75
  interactive: !!isInteractive,
63
- compact: !!compact,
64
76
  'focus-visible': !!visuallyFocused,
65
77
  disabled: !!visuallyDisabled,
78
+ size,
66
79
  },
67
80
  });
68
81
 
@@ -74,9 +87,10 @@ export const CardTitle = withClassName(
74
87
  surface: 'ambient',
75
88
  gap: 'sm',
76
89
  col: true,
77
- rounded: 'sm',
90
+ rounded: 'md',
78
91
  }),
79
92
  cls.title,
93
+ '@mode-bold',
80
94
  );
81
95
 
82
96
  const CardContentRoot = withClassName(
@@ -25,25 +25,21 @@ export function CheckboxField({
25
25
  const [props, _, tools] = useField({ name, required, type: 'checkbox' });
26
26
  const id = useIdOrGenerated(providedId);
27
27
  return (
28
- <Field horizontal className={className}>
29
- <Field.Control>
30
- <Checkbox
31
- {...props}
32
- checked={props.checked}
33
- onCheckedChange={(v) => {
34
- tools.setValue(!!v);
35
- }}
36
- id={id}
37
- aria-describedby={description ? `${id}-description` : undefined}
38
- {...rest}
39
- />
40
- </Field.Control>
41
- {label && <Field.Label htmlFor={id}>{label}</Field.Label>}
42
- {description && (
43
- <Field.Description id={`${id}-description`}>
44
- {description}
45
- </Field.Description>
46
- )}
28
+ <Field horizontal className={className} id={id}>
29
+ <Field.Control
30
+ render={
31
+ <Checkbox
32
+ {...props}
33
+ checked={props.checked}
34
+ onCheckedChange={(v) => {
35
+ tools.setValue(!!v);
36
+ }}
37
+ {...rest}
38
+ />
39
+ }
40
+ />
41
+ {label && <Field.Label>{label}</Field.Label>}
42
+ {description && <Field.Description>{description}</Field.Description>}
47
43
  </Field>
48
44
  );
49
45
  }
@@ -29,54 +29,43 @@ export function EmojiField({
29
29
  const id = useIdOrGenerated(providedId);
30
30
  const [open, setOpen] = useState(false);
31
31
  return (
32
- <Field horizontal className={className}>
33
- <Field.Control>
34
- <Popover open={open} onOpenChange={setOpen}>
35
- <input
36
- type="hidden"
37
- {...props}
38
- id={id}
39
- aria-describedby={description ? `${id}-description` : undefined}
40
- />
41
- <Popover.Trigger
42
- render={
43
- <Button
44
- id={`${id}-trigger`}
45
- aria-label="Select emoji"
46
- size="wrapper"
47
- {...rest}
48
- >
49
- <Button.Icon className={cls.triggerIcon}>
50
- {props.value || <Icon name="smile" />}
51
- </Button.Icon>
52
- </Button>
32
+ <Field horizontal className={className} id={id}>
33
+ <Popover open={open} onOpenChange={setOpen}>
34
+ <Field.Control render={<input type="hidden" {...props} />} />
35
+ <Popover.Trigger
36
+ render={
37
+ <Button
38
+ id={`${id}-trigger`}
39
+ aria-label="Select emoji"
40
+ size="wrapper"
41
+ {...rest}
42
+ >
43
+ <Button.Icon className={cls.triggerIcon}>
44
+ {props.value || <Icon name="smile" />}
45
+ </Button.Icon>
46
+ </Button>
47
+ }
48
+ />
49
+ <Popover.Content>
50
+ <EmojiPicker
51
+ onValueChange={(v) => {
52
+ tools.setValue(v);
53
+ setOpen(false);
54
+ }}
55
+ onClear={
56
+ required
57
+ ? undefined
58
+ : () => {
59
+ tools.setValue('');
60
+ setOpen(false);
61
+ }
53
62
  }
63
+ id={id}
54
64
  />
55
- <Popover.Content>
56
- <EmojiPicker
57
- onValueChange={(v) => {
58
- tools.setValue(v);
59
- setOpen(false);
60
- }}
61
- onClear={
62
- required
63
- ? undefined
64
- : () => {
65
- tools.setValue('');
66
- setOpen(false);
67
- }
68
- }
69
- id={id}
70
- />
71
- </Popover.Content>
72
- </Popover>
73
- </Field.Control>
74
- {label && <Field.Label htmlFor={id}>{label}</Field.Label>}
75
- {description && (
76
- <Field.Description id={`${id}-description`}>
77
- {description}
78
- </Field.Description>
79
- )}
65
+ </Popover.Content>
66
+ </Popover>
67
+ {label && <Field.Label>{label}</Field.Label>}
68
+ {description && <Field.Description>{description}</Field.Description>}
80
69
  </Field>
81
70
  );
82
71
  }
@@ -8,6 +8,10 @@
8
8
  align-items: center;
9
9
  grid-template-areas: 'control label' 'description description';
10
10
  grid-template-columns: auto 1fr;
11
+ &[data-reverse] {
12
+ grid-template-areas: 'label control' 'description description';
13
+ grid-template-columns: auto 1fr;
14
+ }
11
15
  }
12
16
 
13
17
  &[data-stretch] {
@@ -18,12 +22,12 @@
18
22
 
19
23
  .control {
20
24
  grid-area: control;
21
- }
22
- :where(.control) {
23
- display: flex;
24
- flex-direction: column;
25
- align-items: center;
26
- gap: var(--m-space-sm);
25
+
26
+ [data-stretch] & {
27
+ align-self: stretch;
28
+ justify-content: stretch;
29
+ width: 100%;
30
+ }
27
31
  }
28
32
 
29
33
  .label {
@@ -38,6 +42,13 @@
38
42
  @apply --mx-fg(var(--m-color-neutral-ink));
39
43
  font-weight: var(--m-text-weight-normal);
40
44
  margin-block-start: --fn-literal(5px);
45
+
46
+ [data-reverse] & {
47
+ margin-inline-start: 0;
48
+ margin-inline-end: calc(
49
+ var(--m-global-shape-roundness) * var(--m-space-md)
50
+ );
51
+ }
41
52
  }
42
53
  }
43
54
 
@@ -1,36 +1,118 @@
1
1
  import clsx from 'clsx';
2
- import { ComponentProps } from 'react';
2
+ import {
3
+ ComponentProps,
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import useMergedRef from '../../hooks/useMergedRef.js';
3
11
  import { SlotDiv } from '../utility/SlotDiv.js';
4
12
  import cls from './Field.module.css';
5
13
 
6
14
  export interface FieldRootProps extends ComponentProps<'div'> {
7
15
  horizontal?: boolean;
8
16
  stretch?: boolean;
17
+ reverse?: boolean;
18
+ /**
19
+ * This ID will be passed to Control, and Label will be associated.
20
+ */
21
+ id?: string;
9
22
  }
10
23
 
11
- function FieldRoot({ className, horizontal, ...props }: FieldRootProps) {
24
+ const FieldContext = createContext<{ id?: string; events: EventTarget }>({
25
+ events: new EventTarget(),
26
+ });
27
+
28
+ function FieldRoot({
29
+ className,
30
+ horizontal,
31
+ stretch,
32
+ reverse,
33
+ id,
34
+ ...props
35
+ }: FieldRootProps) {
36
+ const [events] = useState(() => new EventTarget());
37
+ return (
38
+ <FieldContext.Provider value={{ id, events }}>
39
+ <SlotDiv
40
+ className={clsx(cls.root, className)}
41
+ data-horizontal={horizontal ? '' : undefined}
42
+ data-stretch={stretch ? '' : undefined}
43
+ data-reverse={reverse ? '' : undefined}
44
+ {...props}
45
+ />
46
+ </FieldContext.Provider>
47
+ );
48
+ }
49
+
50
+ function FieldControl({
51
+ className,
52
+ ref,
53
+ ...props
54
+ }: ComponentProps<typeof SlotDiv>) {
55
+ const innerRef = useRef<HTMLElement>(null);
56
+ const finalRef = useMergedRef(ref, innerRef);
57
+ const { id, events } = useContext(FieldContext);
58
+
59
+ useEffect(() => {
60
+ if (!id) return;
61
+
62
+ function onDescriptionVisible(event: CustomEvent) {
63
+ if (event.detail === true) {
64
+ innerRef.current?.setAttribute('aria-describedby', `${id}-description`);
65
+ } else {
66
+ innerRef.current?.removeAttribute('aria-describedby');
67
+ }
68
+ }
69
+
70
+ events.addEventListener(
71
+ 'description-visible',
72
+ onDescriptionVisible as EventListener,
73
+ );
74
+ return () => {
75
+ events.removeEventListener(
76
+ 'description-visible',
77
+ onDescriptionVisible as EventListener,
78
+ );
79
+ };
80
+ }, [events, id]);
81
+
12
82
  return (
13
83
  <SlotDiv
14
- className={clsx(cls.root, className)}
15
- data-horizontal={horizontal ? '' : undefined}
16
- data-stretch={props.stretch ? '' : undefined}
84
+ className={clsx(cls.control, className)}
85
+ id={id}
86
+ ref={finalRef}
17
87
  {...props}
18
88
  />
19
89
  );
20
90
  }
21
91
 
22
- function FieldControl({ className, ...props }: ComponentProps<typeof SlotDiv>) {
23
- return <SlotDiv className={clsx(cls.control, className)} {...props} />;
24
- }
25
-
26
92
  function FieldLabel({ className, ...props }: ComponentProps<'label'>) {
27
- return <label className={clsx(cls.label, className)} {...props} />;
93
+ const { id } = useContext(FieldContext);
94
+ return (
95
+ <label className={clsx(cls.label, className)} htmlFor={id} {...props} />
96
+ );
28
97
  }
29
98
 
30
99
  function FieldDescription({ className, ...props }: ComponentProps<'div'>) {
100
+ const { id, events } = useContext(FieldContext);
101
+
102
+ useEffect(() => {
103
+ events.dispatchEvent(
104
+ new CustomEvent('description-visible', { detail: true }),
105
+ );
106
+ return () => {
107
+ events.dispatchEvent(
108
+ new CustomEvent('description-visible', { detail: false }),
109
+ );
110
+ };
111
+ }, [events]);
31
112
  return (
32
113
  <div
33
114
  className={clsx('@mode-dense', cls.description, className)}
115
+ id={id ? `${id}-description` : undefined}
34
116
  {...props}
35
117
  />
36
118
  );
@@ -26,28 +26,27 @@ export function NumberStepperField({
26
26
  description,
27
27
  ...rest
28
28
  }: NumberStepperFieldProps) {
29
- const [_, field, tools] = useField({ name });
29
+ const [props, field, tools] = useField({ name });
30
30
  const id = useIdOrGenerated(providedId);
31
31
  return (
32
- <Field className={className} horizontal>
33
- {label && <Field.Label htmlFor={id}>{label}</Field.Label>}
34
- <Field.Control>
35
- <NumberStepper
36
- value={field.value}
37
- onChange={(v) => {
38
- tools.setValue(v);
39
- onChange?.(v);
40
- }}
41
- id={id}
42
- aria-describedby={description ? `${id}-description` : undefined}
43
- {...rest}
44
- />
45
- </Field.Control>
46
- {description && (
47
- <Field.Description id={`${id}-description`}>
48
- {description}
49
- </Field.Description>
50
- )}
32
+ <Field className={className} id={id}>
33
+ {label && <Field.Label>{label}</Field.Label>}
34
+ <Field.Control
35
+ render={(composed) => (
36
+ <>
37
+ <input type="hidden" {...props} {...composed} />
38
+ <NumberStepper
39
+ value={field.value}
40
+ onChange={(v) => {
41
+ tools.setValue(v);
42
+ onChange?.(v);
43
+ }}
44
+ {...rest}
45
+ />
46
+ </>
47
+ )}
48
+ />
49
+ {description && <Field.Description>{description}</Field.Description>}
51
50
  </Field>
52
51
  );
53
52
  }
@@ -25,25 +25,21 @@ export function SwitchField({
25
25
  const [props, _, tools] = useField({ name, required, type: 'checkbox' });
26
26
  const id = useIdOrGenerated(providedId);
27
27
  return (
28
- <Field horizontal className={className}>
29
- <Field.Control>
30
- <Switch
31
- {...props}
32
- checked={props.checked}
33
- onCheckedChange={(v) => {
34
- tools.setValue(!!v);
35
- }}
36
- id={id}
37
- aria-describedby={description ? `${id}-description` : undefined}
38
- {...rest}
39
- />
40
- </Field.Control>
41
- {label && <Field.Label htmlFor={id}>{label}</Field.Label>}
42
- {description && (
43
- <Field.Description id={`${id}-description`}>
44
- {description}
45
- </Field.Description>
46
- )}
28
+ <Field horizontal className={className} id={id}>
29
+ <Field.Control
30
+ render={
31
+ <Switch
32
+ {...props}
33
+ checked={props.checked}
34
+ onCheckedChange={(v) => {
35
+ tools.setValue(!!v);
36
+ }}
37
+ {...rest}
38
+ />
39
+ }
40
+ />
41
+ {label && <Field.Label>{label}</Field.Label>}
42
+ {description && <Field.Description>{description}</Field.Description>}
47
43
  </Field>
48
44
  );
49
45
  }
@@ -63,26 +63,20 @@ export const TextField = function TextField({
63
63
  }, [autoFocusDelay]);
64
64
 
65
65
  return (
66
- <Field className={className} stretch ref={ref}>
67
- {label && <Field.Label htmlFor={id}>{label}</Field.Label>}
66
+ <Field className={className} stretch ref={ref} id={id}>
67
+ {label && <Field.Label>{label}</Field.Label>}
68
68
  <Field.Control
69
69
  render={
70
70
  <Input
71
71
  {...props}
72
72
  {...rest}
73
- id={id}
74
73
  autoFocus={autoFocus}
75
74
  className={clsx(cls.input, inputClassName)}
76
75
  ref={useMergedRef(innerInputRef, inputRef || emptyRef)}
77
- aria-describedby={description ? `${id}-description` : undefined}
78
76
  />
79
77
  }
80
78
  />
81
- {description && (
82
- <Field.Description id={`${id}-description`}>
83
- {description}
84
- </Field.Description>
85
- )}
79
+ {description && <Field.Description>{description}</Field.Description>}
86
80
  </Field>
87
81
  );
88
82
  };
@@ -129,8 +123,8 @@ export function TextAreaField({
129
123
  const id = useIdOrGenerated(providedId);
130
124
 
131
125
  return (
132
- <Field stretch className={className} ref={ref}>
133
- {label && <Field.Label htmlFor={id}>{label}</Field.Label>}
126
+ <Field stretch className={className} ref={ref} id={id}>
127
+ {label && <Field.Label>{label}</Field.Label>}
134
128
  <Field.Control
135
129
  render={
136
130
  <TextArea
@@ -138,17 +132,11 @@ export function TextAreaField({
138
132
  {...props}
139
133
  {...rest}
140
134
  className={clsx(cls.input, textAreaClassName)}
141
- id={id}
142
135
  onKeyDown={onKeyDownInner}
143
- aria-describedby={description ? `${id}-description` : undefined}
144
136
  />
145
137
  }
146
138
  />
147
- {description && (
148
- <Field.Description id={`${id}-description`}>
149
- {description}
150
- </Field.Description>
151
- )}
139
+ {description && <Field.Description>{description}</Field.Description>}
152
140
  </Field>
153
141
  );
154
142
  }
@@ -1,5 +1,6 @@
1
1
  import { useField } from 'formik';
2
2
  import { ReactNode } from 'react';
3
+ import { useIdOrGenerated } from '../../hooks/useIdOrGenerated.js';
3
4
  import { ToggleGroup, ToggleGroupProps } from '../toggleGroup/toggleGroup.js';
4
5
  import { Field } from './Field.js';
5
6
 
@@ -22,36 +23,33 @@ function ToggleGroupFieldDefault({
22
23
  type = 'single',
23
24
  multiple,
24
25
  description,
25
- id,
26
+ id: providedId,
26
27
  ...props
27
28
  }: ToggleGroupFieldProps<string>) {
29
+ const id = useIdOrGenerated(providedId);
28
30
  const [fieldProps, _, tools] = useField({ name, required, ...props });
29
31
 
30
32
  return (
31
- <Field>
32
- {label && <Field.Label htmlFor={id}>{label}</Field.Label>}
33
- <Field.Control>
34
- <ToggleGroup
35
- {...fieldProps}
36
- onValueChange={(v) => {
37
- if (multiple || type === 'multiple') {
38
- tools.setValue(v);
39
- } else {
40
- tools.setValue(v[0]);
41
- }
42
- }}
43
- multiple={multiple || type === 'multiple'}
44
- {...props}
45
- id={id}
46
- className={className}
47
- aria-describedby={description ? `${id}-description` : undefined}
48
- />
49
- </Field.Control>
50
- {description && (
51
- <Field.Description id={`${id}-description`}>
52
- {description}
53
- </Field.Description>
54
- )}
33
+ <Field id={id}>
34
+ {label && <Field.Label>{label}</Field.Label>}
35
+ <Field.Control
36
+ render={
37
+ <ToggleGroup
38
+ {...fieldProps}
39
+ onValueChange={(v) => {
40
+ if (multiple || type === 'multiple') {
41
+ tools.setValue(v);
42
+ } else {
43
+ tools.setValue(v[0]);
44
+ }
45
+ }}
46
+ multiple={multiple || type === 'multiple'}
47
+ {...props}
48
+ className={className}
49
+ />
50
+ }
51
+ />
52
+ {description && <Field.Description>{description}</Field.Description>}
55
53
  </Field>
56
54
  );
57
55
  }