@signal9/era-ui 4.3.1 → 4.4.0
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.
- package/dist/apps/notes/note-editor.svelte +21 -0
- package/dist/apps/notes/notes.svelte +13 -19
- package/dist/era-ui.css +1 -1
- package/dist/generated-docs/input.md +1 -0
- package/dist/generated-docs/llms-full.txt +1 -0
- package/dist/generated-docs/manifest.json +1 -1
- package/dist/styles/density.css +3 -0
- package/dist/ui/button/variants.js +9 -0
- package/dist/ui/combobox/combobox-input.svelte +5 -1
- package/dist/ui/command/command-input.svelte +18 -28
- package/dist/ui/date-field/date-field-input.svelte +4 -1
- package/dist/ui/date-picker/date-picker-input.svelte +4 -1
- package/dist/ui/date-range-field/date-range-field-input.svelte +4 -1
- package/dist/ui/date-range-picker/date-range-picker-input.svelte +4 -1
- package/dist/ui/input/index.d.ts +1 -0
- package/dist/ui/input/index.js +3 -0
- package/dist/ui/input/input.svelte +22 -39
- package/dist/ui/input/input.svelte.d.ts +2 -45
- package/dist/ui/input/textarea.svelte +6 -1
- package/dist/ui/input/variants.d.ts +153 -0
- package/dist/ui/input/variants.js +78 -0
- package/dist/ui/time-field/time-field-input.svelte +4 -1
- package/dist/ui/time-range-field/time-range-field-input.svelte +4 -1
- package/package.json +1 -1
package/dist/styles/density.css
CHANGED
|
@@ -199,6 +199,9 @@
|
|
|
199
199
|
--era-xs-inset-lg: calc(
|
|
200
200
|
(var(--era-h-lg) - var(--era-h-xs)) / 2
|
|
201
201
|
); /* 9/12/15 — center an xs control inside an lg bar (= rd-lg − rd-xs) */
|
|
202
|
+
--era-sm-inset-md: calc(
|
|
203
|
+
(var(--era-h-md) - var(--era-h-sm)) / 2
|
|
204
|
+
); /* 1/2/3 — center an sm control inside an md field (= rd-md − rd-sm), e.g. the date picker's trailing trigger */
|
|
202
205
|
--era-sm-inset-lg: calc(
|
|
203
206
|
(var(--era-h-lg) - var(--era-h-sm)) / 2
|
|
204
207
|
); /* 5/7/9 = sp − 3 — inset an sm panel (drag bar) concentrically in an lg pane (= rd-lg − rd-sm) */
|
|
@@ -71,6 +71,15 @@ export const buttonVariants = tv({
|
|
|
71
71
|
// the box: sp+4 in xxs (pad 3), sp+2 in xs/icon (pad 2). Scoped to icon:true
|
|
72
72
|
// so text+icon buttons keep their h-xs glyph. The descendant selector
|
|
73
73
|
// outranks the consumer's own size-* on the svg, so no call site changes.
|
|
74
|
+
//
|
|
75
|
+
// default/sm were DESCRIBED by the comment above but had no rule, so an
|
|
76
|
+
// icon button at either size fell through to Lucide's own 24px default
|
|
77
|
+
// unless the call site happened to pass a size class. That is how the
|
|
78
|
+
// notes "new note" button rendered a 24px glyph in a 24px box — flush,
|
|
79
|
+
// no padding at all. Stating them makes the documented intent real and
|
|
80
|
+
// removes the dependency on every call site remembering.
|
|
81
|
+
{ icon: true, size: 'default', class: '[&_svg]:size-(--era-h-xs)' },
|
|
82
|
+
{ icon: true, size: 'sm', class: '[&_svg]:size-(--era-h-xs)' },
|
|
74
83
|
{ icon: true, size: 'xxs', class: '[&_svg]:size-(--era-icon-xxs)' },
|
|
75
84
|
{ icon: true, size: 'xs', class: '[&_svg]:size-(--era-icon-xs)' },
|
|
76
85
|
{ icon: true, size: 'icon', class: '[&_svg]:size-(--era-icon-xs)' },
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Combobox } from 'bits-ui';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from '../input/variants.js';
|
|
4
5
|
|
|
5
6
|
let { ref = $bindable(null), class: className, ...restProps }: Combobox.InputProps = $props();
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
<Combobox.Input
|
|
9
10
|
bind:ref
|
|
11
|
+
data-input-field
|
|
10
12
|
class={cn(
|
|
11
|
-
|
|
13
|
+
inputVariants(),
|
|
14
|
+
// The open list is docked to the field, so the seam between them opens up.
|
|
15
|
+
'data-[state=open]:rounded-b-none data-[state=open]:shadow-(--era-shadow-pressed)',
|
|
12
16
|
className
|
|
13
17
|
)}
|
|
14
18
|
{...restProps}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Command } from 'bits-ui';
|
|
3
3
|
import type { Component } from 'svelte';
|
|
4
4
|
import type { IconProps } from '@lucide/svelte';
|
|
5
|
-
import
|
|
5
|
+
import Input from '../input/input.svelte';
|
|
6
6
|
|
|
7
7
|
let {
|
|
8
8
|
ref = $bindable(null),
|
|
@@ -16,30 +16,20 @@
|
|
|
16
16
|
} = $props();
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<Command.Input
|
|
37
|
-
bind:ref
|
|
38
|
-
bind:value
|
|
39
|
-
class={cn(
|
|
40
|
-
'flex h-(--era-h-md) w-full era-interactive rounded-(--era-rd-md) bg-(--era-field-bg) px-(--era-field-px) text-body text-fg shadow-(--era-shadow-well) placeholder:text-muted hover:bg-(--era-highlight) focus:bg-(--era-highlight)',
|
|
41
|
-
className
|
|
42
|
-
)}
|
|
43
|
-
{...restProps}
|
|
44
|
-
/>
|
|
45
|
-
{/if}
|
|
19
|
+
<!--
|
|
20
|
+
The search line of a command palette is a plain era Input, so this renders the
|
|
21
|
+
real component instead of a copy of its classes. It used to restate the whole
|
|
22
|
+
field chrome AND re-implement the icon branch — a second Input in all but
|
|
23
|
+
name, free to drift from the first, which it had.
|
|
24
|
+
|
|
25
|
+
`child` is what makes the delegation possible: bits hands us its behaviour as
|
|
26
|
+
props (the combobox roles, the active-descendant wiring, the ref attachment)
|
|
27
|
+
and we spread them onto our own element. `value` is bound in both places on
|
|
28
|
+
purpose — bits does not bind it for us in the child branch, so the Input's
|
|
29
|
+
binding is what feeds the palette's search state.
|
|
30
|
+
-->
|
|
31
|
+
<Command.Input bind:ref bind:value {...restProps}>
|
|
32
|
+
{#snippet child({ props })}
|
|
33
|
+
<Input bind:value {icon} class={className} {...props} />
|
|
34
|
+
{/snippet}
|
|
35
|
+
</Command.Input>
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { DateField } from 'bits-ui';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from '../input/variants.js';
|
|
4
5
|
|
|
5
6
|
let { ref = $bindable(null), class: className, ...restProps }: DateField.InputProps = $props();
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
<DateField.Input
|
|
9
10
|
bind:ref
|
|
11
|
+
data-input-field
|
|
10
12
|
class={cn(
|
|
11
|
-
|
|
13
|
+
inputVariants(),
|
|
14
|
+
'items-center disabled:opacity-50 data-[invalid]:text-destructive',
|
|
12
15
|
className
|
|
13
16
|
)}
|
|
14
17
|
{...restProps}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { DatePicker } from 'bits-ui';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from '../input/variants.js';
|
|
4
5
|
|
|
5
6
|
let { ref = $bindable(null), class: className, ...restProps }: DatePicker.InputProps = $props();
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
<DatePicker.Input
|
|
9
10
|
bind:ref
|
|
11
|
+
data-input-field
|
|
10
12
|
class={cn(
|
|
11
|
-
|
|
13
|
+
inputVariants({ pad: 'trailing' }),
|
|
14
|
+
'items-center disabled:opacity-50 data-[invalid]:text-destructive',
|
|
12
15
|
className
|
|
13
16
|
)}
|
|
14
17
|
{...restProps}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { DateRangeField } from 'bits-ui';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from '../input/variants.js';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
ref = $bindable(null),
|
|
@@ -11,8 +12,10 @@
|
|
|
11
12
|
|
|
12
13
|
<DateRangeField.Input
|
|
13
14
|
bind:ref
|
|
15
|
+
data-input-field
|
|
14
16
|
class={cn(
|
|
15
|
-
|
|
17
|
+
inputVariants(),
|
|
18
|
+
'items-center disabled:opacity-50 data-[invalid]:text-destructive',
|
|
16
19
|
className
|
|
17
20
|
)}
|
|
18
21
|
{...restProps}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { DateRangePicker } from 'bits-ui';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from '../input/variants.js';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
ref = $bindable(null),
|
|
@@ -11,8 +12,10 @@
|
|
|
11
12
|
|
|
12
13
|
<DateRangePicker.Input
|
|
13
14
|
bind:ref
|
|
15
|
+
data-input-field
|
|
14
16
|
class={cn(
|
|
15
|
-
|
|
17
|
+
inputVariants({ pad: 'trailing' }),
|
|
18
|
+
'items-center disabled:opacity-50 data-[invalid]:text-destructive',
|
|
16
19
|
className
|
|
17
20
|
)}
|
|
18
21
|
{...restProps}
|
package/dist/ui/input/index.d.ts
CHANGED
package/dist/ui/input/index.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { default as Input } from './input.svelte';
|
|
2
2
|
export { default as Textarea } from './textarea.svelte';
|
|
3
|
+
// The field chrome itself, so a component that builds its own field composes
|
|
4
|
+
// this rather than restating it. See variants.ts.
|
|
5
|
+
export { inputVariants } from './variants.js';
|
|
@@ -1,42 +1,7 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import { cn, tv } from '../../utils/index.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The field shell — the chrome an input paints, minus the text styling.
|
|
6
|
-
*
|
|
7
|
-
* Split out as a variant because an `icon` input has to move that chrome onto
|
|
8
|
-
* a WRAPPER (the glyph and the <input> sit inside one field), while a plain
|
|
9
|
-
* one keeps it on the <input> itself. Both spellings therefore have to
|
|
10
|
-
* describe the same box, and a variant is how they stay one description.
|
|
11
|
-
*/
|
|
12
|
-
export const inputVariants = tv({
|
|
13
|
-
base: 'flex w-full era-interactive text-body text-fg',
|
|
14
|
-
variants: {
|
|
15
|
-
/**
|
|
16
|
-
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
17
|
-
* padding. For an input nested in a container that is ALREADY the field:
|
|
18
|
-
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
19
|
-
* second box inside the first, and at the same tier it fills its parent
|
|
20
|
-
* edge to edge so the two have no gap between them.
|
|
21
|
-
*/
|
|
22
|
-
bare: {
|
|
23
|
-
false:
|
|
24
|
-
'h-(--era-h-md) rounded-(--era-rd-md) bg-(--era-field-bg) px-(--era-field-px) shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) focus:bg-(--era-highlight)',
|
|
25
|
-
// h-full, NOT the md tier: a bare input's container IS the field, so it
|
|
26
|
-
// takes that container's height. Keeping h-md here made a bare input
|
|
27
|
-
// inside an h-md Bar overflow it by the bar's border — a −0.5px gap,
|
|
28
|
-
// which is the even-gap law failing by a hair rather than obviously.
|
|
29
|
-
// Implies a parent with a definite height, which is the only place
|
|
30
|
-
// `bare` makes sense anyway.
|
|
31
|
-
true: 'h-full bg-transparent px-0 shadow-none'
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
defaultVariants: { bare: false }
|
|
35
|
-
});
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
1
|
<script lang="ts">
|
|
39
2
|
import type { Component } from 'svelte';
|
|
3
|
+
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from './variants.js';
|
|
40
5
|
import type { IconProps } from '@lucide/svelte';
|
|
41
6
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
42
7
|
|
|
@@ -45,6 +10,7 @@
|
|
|
45
10
|
value = $bindable(''),
|
|
46
11
|
icon = null,
|
|
47
12
|
bare = false,
|
|
13
|
+
reveal = false,
|
|
48
14
|
class: className,
|
|
49
15
|
...restProps
|
|
50
16
|
}: HTMLInputAttributes & {
|
|
@@ -53,6 +19,8 @@
|
|
|
53
19
|
icon?: Component<IconProps> | null;
|
|
54
20
|
/** Drop the field chrome — for an input inside a container that is already the field. */
|
|
55
21
|
bare?: boolean;
|
|
22
|
+
/** Hold the field chrome back until hover — for a title or label that IS the content. */
|
|
23
|
+
reveal?: boolean;
|
|
56
24
|
} = $props();
|
|
57
25
|
|
|
58
26
|
// No era-text-trim on the <input> itself, in either branch: an <input> clips to
|
|
@@ -60,6 +28,12 @@
|
|
|
60
28
|
// the ascenders and descenders off what you type (a "d" renders as an "o"). The
|
|
61
29
|
// fixed tier height centres the text on its own. See styles/index.css.
|
|
62
30
|
const textClass = 'text-body text-fg placeholder:text-muted';
|
|
31
|
+
|
|
32
|
+
// The marker says WHICH field this is, not just that it is one. `bare` and
|
|
33
|
+
// `reveal` both render chrome-less at rest, so a test that only sees "no
|
|
34
|
+
// chrome" cannot tell a sanctioned exception from a regression — which is the
|
|
35
|
+
// hole the Notes fields sat in. Naming the kind closes it.
|
|
36
|
+
const kind = $derived(reveal ? 'reveal' : bare ? 'bare' : '');
|
|
63
37
|
</script>
|
|
64
38
|
|
|
65
39
|
{#if icon}
|
|
@@ -67,7 +41,15 @@
|
|
|
67
41
|
<!-- The chrome moves to the wrapper so the glyph sits INSIDE the field. The
|
|
68
42
|
glyph is on the icon tier of the field's own height, and the gap between
|
|
69
43
|
it and the text is the universal inter-element gap. -->
|
|
70
|
-
|
|
44
|
+
<!-- data-input-field marks whichever element actually PAINTS the field. With
|
|
45
|
+
an icon that is this wrapper; without one it is the <input> itself. Tests
|
|
46
|
+
and consumers can then target the field directly instead of guessing from
|
|
47
|
+
the DOM shape — guessing breaks on a `bare` input, whose parent is an
|
|
48
|
+
arbitrary container that may carry its own fill. -->
|
|
49
|
+
<div
|
|
50
|
+
data-input-field={kind}
|
|
51
|
+
class={cn(inputVariants({ bare, reveal }), 'items-center gap-(--era-gap)', className)}
|
|
52
|
+
>
|
|
71
53
|
<Icon class="size-(--era-h-xs) shrink-0 text-muted" />
|
|
72
54
|
<input
|
|
73
55
|
bind:this={ref}
|
|
@@ -80,7 +62,8 @@
|
|
|
80
62
|
<input
|
|
81
63
|
bind:this={ref}
|
|
82
64
|
bind:value
|
|
83
|
-
|
|
65
|
+
data-input-field={kind}
|
|
66
|
+
class={cn(inputVariants({ bare, reveal }), textClass, className)}
|
|
84
67
|
{...restProps}
|
|
85
68
|
/>
|
|
86
69
|
{/if}
|
|
@@ -1,48 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The field shell — the chrome an input paints, minus the text styling.
|
|
3
|
-
*
|
|
4
|
-
* Split out as a variant because an `icon` input has to move that chrome onto
|
|
5
|
-
* a WRAPPER (the glyph and the <input> sit inside one field), while a plain
|
|
6
|
-
* one keeps it on the <input> itself. Both spellings therefore have to
|
|
7
|
-
* describe the same box, and a variant is how they stay one description.
|
|
8
|
-
*/
|
|
9
|
-
export declare const inputVariants: import("tailwind-variants").TVReturnType<{
|
|
10
|
-
/**
|
|
11
|
-
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
12
|
-
* padding. For an input nested in a container that is ALREADY the field:
|
|
13
|
-
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
14
|
-
* second box inside the first, and at the same tier it fills its parent
|
|
15
|
-
* edge to edge so the two have no gap between them.
|
|
16
|
-
*/
|
|
17
|
-
bare: {
|
|
18
|
-
false: "h-(--era-h-md) rounded-(--era-rd-md) bg-(--era-field-bg) px-(--era-field-px) shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) focus:bg-(--era-highlight)";
|
|
19
|
-
true: "h-full bg-transparent px-0 shadow-none";
|
|
20
|
-
};
|
|
21
|
-
}, undefined, "flex w-full era-interactive text-body text-fg", {
|
|
22
|
-
/**
|
|
23
|
-
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
24
|
-
* padding. For an input nested in a container that is ALREADY the field:
|
|
25
|
-
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
26
|
-
* second box inside the first, and at the same tier it fills its parent
|
|
27
|
-
* edge to edge so the two have no gap between them.
|
|
28
|
-
*/
|
|
29
|
-
bare: {
|
|
30
|
-
false: "h-(--era-h-md) rounded-(--era-rd-md) bg-(--era-field-bg) px-(--era-field-px) shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) focus:bg-(--era-highlight)";
|
|
31
|
-
true: "h-full bg-transparent px-0 shadow-none";
|
|
32
|
-
};
|
|
33
|
-
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
34
|
-
/**
|
|
35
|
-
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
36
|
-
* padding. For an input nested in a container that is ALREADY the field:
|
|
37
|
-
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
38
|
-
* second box inside the first, and at the same tier it fills its parent
|
|
39
|
-
* edge to edge so the two have no gap between them.
|
|
40
|
-
*/
|
|
41
|
-
bare: {
|
|
42
|
-
false: "h-(--era-h-md) rounded-(--era-rd-md) bg-(--era-field-bg) px-(--era-field-px) shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) focus:bg-(--era-highlight)";
|
|
43
|
-
true: "h-full bg-transparent px-0 shadow-none";
|
|
44
|
-
};
|
|
45
|
-
}, undefined>>;
|
|
46
1
|
import type { Component } from 'svelte';
|
|
47
2
|
import type { IconProps } from '@lucide/svelte';
|
|
48
3
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
@@ -52,6 +7,8 @@ type $$ComponentProps = HTMLInputAttributes & {
|
|
|
52
7
|
icon?: Component<IconProps> | null;
|
|
53
8
|
/** Drop the field chrome — for an input inside a container that is already the field. */
|
|
54
9
|
bare?: boolean;
|
|
10
|
+
/** Hold the field chrome back until hover — for a title or label that IS the content. */
|
|
11
|
+
reveal?: boolean;
|
|
55
12
|
};
|
|
56
13
|
declare const Input: Component<$$ComponentProps, {}, "value" | "ref">;
|
|
57
14
|
type Input = ReturnType<typeof Input>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from './variants.js';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
ref = $bindable(null),
|
|
@@ -15,8 +16,12 @@
|
|
|
15
16
|
<textarea
|
|
16
17
|
bind:this={ref}
|
|
17
18
|
bind:value
|
|
19
|
+
data-input-field
|
|
18
20
|
class={cn(
|
|
19
|
-
|
|
21
|
+
inputVariants({ height: 'auto' }),
|
|
22
|
+
// It grows, so the vertical inset has to be stated: the tier height is what
|
|
23
|
+
// centres one line of text in every other field, and there isn't one here.
|
|
24
|
+
'resize-y py-(--era-inset-md)',
|
|
20
25
|
className
|
|
21
26
|
)}
|
|
22
27
|
{...restProps}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE field. Every text field in the library paints this and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* This module exists because it did not: the chrome below was hand-copied into
|
|
5
|
+
* Input, Textarea, CommandInput, ComboboxInput and all six date/time field
|
|
6
|
+
* inputs, and the copies had already drifted apart — some used `focus:`, others
|
|
7
|
+
* `focus-within:`, some omitted `era-interactive`, the date picker used a
|
|
8
|
+
* different leading padding, and the Notes fields had dropped the chrome
|
|
9
|
+
* entirely. Nine restatements of one idea is nine chances to be inconsistent,
|
|
10
|
+
* so there is now one statement and the rest import it.
|
|
11
|
+
*
|
|
12
|
+
* A component that needs something different asks for it as a VARIANT here,
|
|
13
|
+
* where every other field can see it — never by respelling the base.
|
|
14
|
+
*
|
|
15
|
+
* `tests/visual/input-consistency.spec.ts` measures the rendered result across
|
|
16
|
+
* routes and fails if any two fields differ.
|
|
17
|
+
*/
|
|
18
|
+
export declare const inputVariants: import("tailwind-variants").TVReturnType<{
|
|
19
|
+
/**
|
|
20
|
+
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
21
|
+
* padding. For an input nested in a container that is ALREADY the field:
|
|
22
|
+
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
23
|
+
* second box inside the first, and at the same tier it fills its parent
|
|
24
|
+
* edge to edge so the two have no gap between them.
|
|
25
|
+
*/
|
|
26
|
+
bare: {
|
|
27
|
+
false: "rounded-(--era-rd-md) bg-(--era-field-bg) shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) focus:bg-(--era-highlight)";
|
|
28
|
+
true: "bg-transparent shadow-none";
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* `reveal` holds the field back until the pointer arrives: no fill and no
|
|
32
|
+
* well at rest, the ordinary resting field on hover, the ordinary focused
|
|
33
|
+
* field on focus. For a field whose value is really the page's own content
|
|
34
|
+
* rather than a form entry — a document title, a renameable label — where
|
|
35
|
+
* a box drawn around it at all times competes with the thing it names.
|
|
36
|
+
*
|
|
37
|
+
* It is deliberately a variant here and not a one-off class on the caller:
|
|
38
|
+
* a revealed field has to reveal THE field, and the only way to guarantee
|
|
39
|
+
* that is for it to be spelled once, next to it.
|
|
40
|
+
*/
|
|
41
|
+
reveal: {
|
|
42
|
+
true: "bg-transparent shadow-none hover:bg-(--era-field-bg) hover:shadow-(--era-shadow-well) focus-within:shadow-(--era-shadow-well) focus:shadow-(--era-shadow-well)";
|
|
43
|
+
false: "";
|
|
44
|
+
};
|
|
45
|
+
/** Vertical size. `auto` is for a field that grows with its content (Textarea). */
|
|
46
|
+
height: {
|
|
47
|
+
md: "h-(--era-h-md)";
|
|
48
|
+
full: "h-full";
|
|
49
|
+
auto: "min-h-(--era-h-md)";
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Side padding. `trailing` is for a field that holds a control at its end
|
|
53
|
+
* (the date picker's calendar trigger): the leading edge keeps the shared
|
|
54
|
+
* text inset so the text still lines up with every other field, and the
|
|
55
|
+
* trailing edge becomes the concentric inset for that sm-tier control, so
|
|
56
|
+
* its gap to the field edge equals its gap above and below.
|
|
57
|
+
*/
|
|
58
|
+
pad: {
|
|
59
|
+
even: "px-(--era-field-px)";
|
|
60
|
+
trailing: "ps-(--era-field-px) pe-(--era-sm-inset-md)";
|
|
61
|
+
none: "px-0";
|
|
62
|
+
};
|
|
63
|
+
}, undefined, "flex w-full era-interactive text-body text-fg placeholder:text-muted", {
|
|
64
|
+
/**
|
|
65
|
+
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
66
|
+
* padding. For an input nested in a container that is ALREADY the field:
|
|
67
|
+
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
68
|
+
* second box inside the first, and at the same tier it fills its parent
|
|
69
|
+
* edge to edge so the two have no gap between them.
|
|
70
|
+
*/
|
|
71
|
+
bare: {
|
|
72
|
+
false: "rounded-(--era-rd-md) bg-(--era-field-bg) shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) focus:bg-(--era-highlight)";
|
|
73
|
+
true: "bg-transparent shadow-none";
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* `reveal` holds the field back until the pointer arrives: no fill and no
|
|
77
|
+
* well at rest, the ordinary resting field on hover, the ordinary focused
|
|
78
|
+
* field on focus. For a field whose value is really the page's own content
|
|
79
|
+
* rather than a form entry — a document title, a renameable label — where
|
|
80
|
+
* a box drawn around it at all times competes with the thing it names.
|
|
81
|
+
*
|
|
82
|
+
* It is deliberately a variant here and not a one-off class on the caller:
|
|
83
|
+
* a revealed field has to reveal THE field, and the only way to guarantee
|
|
84
|
+
* that is for it to be spelled once, next to it.
|
|
85
|
+
*/
|
|
86
|
+
reveal: {
|
|
87
|
+
true: "bg-transparent shadow-none hover:bg-(--era-field-bg) hover:shadow-(--era-shadow-well) focus-within:shadow-(--era-shadow-well) focus:shadow-(--era-shadow-well)";
|
|
88
|
+
false: "";
|
|
89
|
+
};
|
|
90
|
+
/** Vertical size. `auto` is for a field that grows with its content (Textarea). */
|
|
91
|
+
height: {
|
|
92
|
+
md: "h-(--era-h-md)";
|
|
93
|
+
full: "h-full";
|
|
94
|
+
auto: "min-h-(--era-h-md)";
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Side padding. `trailing` is for a field that holds a control at its end
|
|
98
|
+
* (the date picker's calendar trigger): the leading edge keeps the shared
|
|
99
|
+
* text inset so the text still lines up with every other field, and the
|
|
100
|
+
* trailing edge becomes the concentric inset for that sm-tier control, so
|
|
101
|
+
* its gap to the field edge equals its gap above and below.
|
|
102
|
+
*/
|
|
103
|
+
pad: {
|
|
104
|
+
even: "px-(--era-field-px)";
|
|
105
|
+
trailing: "ps-(--era-field-px) pe-(--era-sm-inset-md)";
|
|
106
|
+
none: "px-0";
|
|
107
|
+
};
|
|
108
|
+
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
109
|
+
/**
|
|
110
|
+
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
111
|
+
* padding. For an input nested in a container that is ALREADY the field:
|
|
112
|
+
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
113
|
+
* second box inside the first, and at the same tier it fills its parent
|
|
114
|
+
* edge to edge so the two have no gap between them.
|
|
115
|
+
*/
|
|
116
|
+
bare: {
|
|
117
|
+
false: "rounded-(--era-rd-md) bg-(--era-field-bg) shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) focus:bg-(--era-highlight)";
|
|
118
|
+
true: "bg-transparent shadow-none";
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* `reveal` holds the field back until the pointer arrives: no fill and no
|
|
122
|
+
* well at rest, the ordinary resting field on hover, the ordinary focused
|
|
123
|
+
* field on focus. For a field whose value is really the page's own content
|
|
124
|
+
* rather than a form entry — a document title, a renameable label — where
|
|
125
|
+
* a box drawn around it at all times competes with the thing it names.
|
|
126
|
+
*
|
|
127
|
+
* It is deliberately a variant here and not a one-off class on the caller:
|
|
128
|
+
* a revealed field has to reveal THE field, and the only way to guarantee
|
|
129
|
+
* that is for it to be spelled once, next to it.
|
|
130
|
+
*/
|
|
131
|
+
reveal: {
|
|
132
|
+
true: "bg-transparent shadow-none hover:bg-(--era-field-bg) hover:shadow-(--era-shadow-well) focus-within:shadow-(--era-shadow-well) focus:shadow-(--era-shadow-well)";
|
|
133
|
+
false: "";
|
|
134
|
+
};
|
|
135
|
+
/** Vertical size. `auto` is for a field that grows with its content (Textarea). */
|
|
136
|
+
height: {
|
|
137
|
+
md: "h-(--era-h-md)";
|
|
138
|
+
full: "h-full";
|
|
139
|
+
auto: "min-h-(--era-h-md)";
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Side padding. `trailing` is for a field that holds a control at its end
|
|
143
|
+
* (the date picker's calendar trigger): the leading edge keeps the shared
|
|
144
|
+
* text inset so the text still lines up with every other field, and the
|
|
145
|
+
* trailing edge becomes the concentric inset for that sm-tier control, so
|
|
146
|
+
* its gap to the field edge equals its gap above and below.
|
|
147
|
+
*/
|
|
148
|
+
pad: {
|
|
149
|
+
even: "px-(--era-field-px)";
|
|
150
|
+
trailing: "ps-(--era-field-px) pe-(--era-sm-inset-md)";
|
|
151
|
+
none: "px-0";
|
|
152
|
+
};
|
|
153
|
+
}, undefined>>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { tv } from '../../utils/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* THE field. Every text field in the library paints this and nothing else.
|
|
4
|
+
*
|
|
5
|
+
* This module exists because it did not: the chrome below was hand-copied into
|
|
6
|
+
* Input, Textarea, CommandInput, ComboboxInput and all six date/time field
|
|
7
|
+
* inputs, and the copies had already drifted apart — some used `focus:`, others
|
|
8
|
+
* `focus-within:`, some omitted `era-interactive`, the date picker used a
|
|
9
|
+
* different leading padding, and the Notes fields had dropped the chrome
|
|
10
|
+
* entirely. Nine restatements of one idea is nine chances to be inconsistent,
|
|
11
|
+
* so there is now one statement and the rest import it.
|
|
12
|
+
*
|
|
13
|
+
* A component that needs something different asks for it as a VARIANT here,
|
|
14
|
+
* where every other field can see it — never by respelling the base.
|
|
15
|
+
*
|
|
16
|
+
* `tests/visual/input-consistency.spec.ts` measures the rendered result across
|
|
17
|
+
* routes and fails if any two fields differ.
|
|
18
|
+
*/
|
|
19
|
+
export const inputVariants = tv({
|
|
20
|
+
base: 'flex w-full era-interactive text-body text-fg placeholder:text-muted',
|
|
21
|
+
variants: {
|
|
22
|
+
/**
|
|
23
|
+
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
24
|
+
* padding. For an input nested in a container that is ALREADY the field:
|
|
25
|
+
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
26
|
+
* second box inside the first, and at the same tier it fills its parent
|
|
27
|
+
* edge to edge so the two have no gap between them.
|
|
28
|
+
*/
|
|
29
|
+
bare: {
|
|
30
|
+
false: 'rounded-(--era-rd-md) bg-(--era-field-bg) shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) focus:bg-(--era-highlight)',
|
|
31
|
+
true: 'bg-transparent shadow-none'
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* `reveal` holds the field back until the pointer arrives: no fill and no
|
|
35
|
+
* well at rest, the ordinary resting field on hover, the ordinary focused
|
|
36
|
+
* field on focus. For a field whose value is really the page's own content
|
|
37
|
+
* rather than a form entry — a document title, a renameable label — where
|
|
38
|
+
* a box drawn around it at all times competes with the thing it names.
|
|
39
|
+
*
|
|
40
|
+
* It is deliberately a variant here and not a one-off class on the caller:
|
|
41
|
+
* a revealed field has to reveal THE field, and the only way to guarantee
|
|
42
|
+
* that is for it to be spelled once, next to it.
|
|
43
|
+
*/
|
|
44
|
+
reveal: {
|
|
45
|
+
true: 'bg-transparent shadow-none hover:bg-(--era-field-bg) hover:shadow-(--era-shadow-well) focus-within:shadow-(--era-shadow-well) focus:shadow-(--era-shadow-well)',
|
|
46
|
+
false: ''
|
|
47
|
+
},
|
|
48
|
+
/** Vertical size. `auto` is for a field that grows with its content (Textarea). */
|
|
49
|
+
height: {
|
|
50
|
+
md: 'h-(--era-h-md)',
|
|
51
|
+
// h-full, NOT the md tier: a bare input's container IS the field, so it
|
|
52
|
+
// takes that container's height. Keeping h-md here made a bare input
|
|
53
|
+
// inside an h-md Bar overflow it by the bar's border — a −0.5px gap,
|
|
54
|
+
// which is the even-gap law failing by a hair rather than obviously.
|
|
55
|
+
full: 'h-full',
|
|
56
|
+
auto: 'min-h-(--era-h-md)'
|
|
57
|
+
},
|
|
58
|
+
/**
|
|
59
|
+
* Side padding. `trailing` is for a field that holds a control at its end
|
|
60
|
+
* (the date picker's calendar trigger): the leading edge keeps the shared
|
|
61
|
+
* text inset so the text still lines up with every other field, and the
|
|
62
|
+
* trailing edge becomes the concentric inset for that sm-tier control, so
|
|
63
|
+
* its gap to the field edge equals its gap above and below.
|
|
64
|
+
*/
|
|
65
|
+
pad: {
|
|
66
|
+
even: 'px-(--era-field-px)',
|
|
67
|
+
trailing: 'ps-(--era-field-px) pe-(--era-sm-inset-md)',
|
|
68
|
+
none: 'px-0'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
compoundVariants: [
|
|
72
|
+
// Chrome and padding travel together: a field with no chrome has no box to
|
|
73
|
+
// pad. Spelling this as a compound rather than leaving it to the caller is
|
|
74
|
+
// what stops `bare` from becoming a second, differently-padded field.
|
|
75
|
+
{ bare: true, class: 'h-full px-0' }
|
|
76
|
+
],
|
|
77
|
+
defaultVariants: { bare: false, reveal: false, height: 'md', pad: 'even' }
|
|
78
|
+
});
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { TimeField } from 'bits-ui';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from '../input/variants.js';
|
|
4
5
|
|
|
5
6
|
let { ref = $bindable(null), class: className, ...restProps }: TimeField.InputProps = $props();
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
<TimeField.Input
|
|
9
10
|
bind:ref
|
|
11
|
+
data-input-field
|
|
10
12
|
class={cn(
|
|
11
|
-
|
|
13
|
+
inputVariants(),
|
|
14
|
+
'items-center disabled:opacity-50 data-[invalid]:text-destructive',
|
|
12
15
|
className
|
|
13
16
|
)}
|
|
14
17
|
{...restProps}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { TimeRangeField } from 'bits-ui';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { inputVariants } from '../input/variants.js';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
ref = $bindable(null),
|
|
@@ -11,8 +12,10 @@
|
|
|
11
12
|
|
|
12
13
|
<TimeRangeField.Input
|
|
13
14
|
bind:ref
|
|
15
|
+
data-input-field
|
|
14
16
|
class={cn(
|
|
15
|
-
|
|
17
|
+
inputVariants(),
|
|
18
|
+
'items-center disabled:opacity-50 data-[invalid]:text-destructive',
|
|
16
19
|
className
|
|
17
20
|
)}
|
|
18
21
|
{...restProps}
|