@signal9/era-ui 4.3.0 → 4.3.2
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/notes.svelte +12 -19
- package/dist/era-ui.css +1 -1
- package/dist/styles/density.css +3 -0
- package/dist/styles/themes.css +28 -18
- 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 +12 -38
- package/dist/ui/input/input.svelte.d.ts +0 -45
- package/dist/ui/input/textarea.svelte +6 -1
- package/dist/ui/input/variants.d.ts +108 -0
- package/dist/ui/input/variants.js +63 -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/themes.css
CHANGED
|
@@ -391,11 +391,17 @@
|
|
|
391
391
|
/* -------------------------------------------------- */
|
|
392
392
|
/* Signalnine — dark (Tomorrow, measured) */
|
|
393
393
|
/* */
|
|
394
|
-
/* The classic imageboard dark theme.
|
|
395
|
-
/*
|
|
396
|
-
/* converted to OKLCH —
|
|
397
|
-
/*
|
|
398
|
-
/*
|
|
394
|
+
/* The classic imageboard dark theme. The accents and */
|
|
395
|
+
/* the upper scale are Tomorrow Night's own values, */
|
|
396
|
+
/* converted to OKLCH — comment and foreground land */
|
|
397
|
+
/* exactly at steps 8 and 11. */
|
|
398
|
+
/* */
|
|
399
|
+
/* The DARK END is deliberately deeper than Tomorrow's. */
|
|
400
|
+
/* Its #1d1f21 page read light next to era's other dark */
|
|
401
|
+
/* themes, so the floor is the default theme's page */
|
|
402
|
+
/* plus a hair and steps 2-7 re-space from there. The */
|
|
403
|
+
/* identity lives in the greys' hue and the accents, not */
|
|
404
|
+
/* in the exact page value. */
|
|
399
405
|
/* */
|
|
400
406
|
/* This replaces a scale that lurched warm through the */
|
|
401
407
|
/* middle so borders read faintly red against cold */
|
|
@@ -413,17 +419,21 @@
|
|
|
413
419
|
[data-theme='signalnine'].dark {
|
|
414
420
|
color-scheme: dark;
|
|
415
421
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
--color-
|
|
422
|
+
/* The floor is era's default-dark page (0.162) plus a hair, not Tomorrow's
|
|
423
|
+
* own #1d1f21 — that read too light beside the other dark themes. Everything
|
|
424
|
+
* from step 2 up re-spaces evenly from the new floor to the comment anchor,
|
|
425
|
+
* so lowering the page did not leave a cliff between it and the panels. */
|
|
426
|
+
--color-1: oklch(0.168 0.005 248); /* #0d0f11 */
|
|
427
|
+
--color-2: oklch(0.225 0.007 258); /* #1a1c1f */
|
|
428
|
+
--color-3: oklch(0.29 0.01 258); /* #282c30 */
|
|
429
|
+
--color-4: oklch(0.355 0.011 258); /* #383c42 */
|
|
430
|
+
--color-5: oklch(0.435 0.011 257);
|
|
421
431
|
--color-6: oklch(0.515 0.01 256);
|
|
422
|
-
--color-7: oklch(0.
|
|
423
|
-
--color-8: oklch(0.677 0.006 200); /* #939899 comment */
|
|
432
|
+
--color-7: oklch(0.596 0.009 255);
|
|
433
|
+
--color-8: oklch(0.677 0.006 200); /* #939899 Tomorrow comment */
|
|
424
434
|
--color-9: oklch(0.73 0.005 190);
|
|
425
435
|
--color-10: oklch(0.78 0.005 180);
|
|
426
|
-
--color-11: oklch(0.83 0.004 157); /* #c5c8c6 foreground */
|
|
436
|
+
--color-11: oklch(0.83 0.004 157); /* #c5c8c6 Tomorrow foreground */
|
|
427
437
|
--color-12: oklch(0.9 0.004 157);
|
|
428
438
|
|
|
429
439
|
/* Border sits a step LOWER than the usual 5. Tomorrow's own rules are
|
|
@@ -432,7 +442,7 @@
|
|
|
432
442
|
* page, inside the 1.7–3.2 the other dark themes hold. */
|
|
433
443
|
--color-border: var(--color-4);
|
|
434
444
|
--color-surface: var(--color-2);
|
|
435
|
-
--color-hover: oklch(0.
|
|
445
|
+
--color-hover: oklch(0.255 0.009 258);
|
|
436
446
|
--color-fg: var(--color-11);
|
|
437
447
|
--color-bright: var(--color-12);
|
|
438
448
|
/* Tomorrow's comment grey, which is what it uses for de-emphasised text. */
|
|
@@ -443,14 +453,14 @@
|
|
|
443
453
|
/* Tomorrow's accents, unmodified. They are pastels, so each takes DARK text
|
|
444
454
|
* — the page colour — rather than white. */
|
|
445
455
|
--color-primary: oklch(0.6975 0.0551 244.3); /* #81a2be blue */
|
|
446
|
-
--color-primary-fg: oklch(0.
|
|
456
|
+
--color-primary-fg: oklch(0.168 0.005 248);
|
|
447
457
|
--color-primary-hover: color-mix(in oklch, var(--color-primary) 85%, black);
|
|
448
458
|
--color-destructive: oklch(0.6308 0.1298 21.4); /* #cc6666 red */
|
|
449
|
-
--color-destructive-fg: oklch(0.
|
|
459
|
+
--color-destructive-fg: oklch(0.168 0.005 248);
|
|
450
460
|
--color-success: oklch(0.7733 0.1095 113.4); /* #b5bd68 green */
|
|
451
|
-
--color-success-fg: oklch(0.
|
|
461
|
+
--color-success-fg: oklch(0.168 0.005 248);
|
|
452
462
|
--color-warning: oklch(0.8462 0.1115 82.9); /* #f0c674 yellow */
|
|
453
|
-
--color-warning-fg: oklch(0.
|
|
463
|
+
--color-warning-fg: oklch(0.168 0.005 248);
|
|
454
464
|
}
|
|
455
465
|
|
|
456
466
|
/* Signalnine — light (yahoo.co.jp, measured) */
|
|
@@ -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
|
|
|
@@ -67,7 +32,15 @@
|
|
|
67
32
|
<!-- The chrome moves to the wrapper so the glyph sits INSIDE the field. The
|
|
68
33
|
glyph is on the icon tier of the field's own height, and the gap between
|
|
69
34
|
it and the text is the universal inter-element gap. -->
|
|
70
|
-
|
|
35
|
+
<!-- data-input-field marks whichever element actually PAINTS the field. With
|
|
36
|
+
an icon that is this wrapper; without one it is the <input> itself. Tests
|
|
37
|
+
and consumers can then target the field directly instead of guessing from
|
|
38
|
+
the DOM shape — guessing breaks on a `bare` input, whose parent is an
|
|
39
|
+
arbitrary container that may carry its own fill. -->
|
|
40
|
+
<div
|
|
41
|
+
data-input-field
|
|
42
|
+
class={cn(inputVariants({ bare }), 'items-center gap-(--era-gap)', className)}
|
|
43
|
+
>
|
|
71
44
|
<Icon class="size-(--era-h-xs) shrink-0 text-muted" />
|
|
72
45
|
<input
|
|
73
46
|
bind:this={ref}
|
|
@@ -80,6 +53,7 @@
|
|
|
80
53
|
<input
|
|
81
54
|
bind:this={ref}
|
|
82
55
|
bind:value
|
|
56
|
+
data-input-field
|
|
83
57
|
class={cn(inputVariants({ bare }), textClass, className)}
|
|
84
58
|
{...restProps}
|
|
85
59
|
/>
|
|
@@ -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';
|
|
@@ -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,108 @@
|
|
|
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
|
+
/** Vertical size. `auto` is for a field that grows with its content (Textarea). */
|
|
31
|
+
height: {
|
|
32
|
+
md: "h-(--era-h-md)";
|
|
33
|
+
full: "h-full";
|
|
34
|
+
auto: "min-h-(--era-h-md)";
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Side padding. `trailing` is for a field that holds a control at its end
|
|
38
|
+
* (the date picker's calendar trigger): the leading edge keeps the shared
|
|
39
|
+
* text inset so the text still lines up with every other field, and the
|
|
40
|
+
* trailing edge becomes the concentric inset for that sm-tier control, so
|
|
41
|
+
* its gap to the field edge equals its gap above and below.
|
|
42
|
+
*/
|
|
43
|
+
pad: {
|
|
44
|
+
even: "px-(--era-field-px)";
|
|
45
|
+
trailing: "ps-(--era-field-px) pe-(--era-sm-inset-md)";
|
|
46
|
+
none: "px-0";
|
|
47
|
+
};
|
|
48
|
+
}, undefined, "flex w-full era-interactive text-body text-fg placeholder:text-muted", {
|
|
49
|
+
/**
|
|
50
|
+
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
51
|
+
* padding. For an input nested in a container that is ALREADY the field:
|
|
52
|
+
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
53
|
+
* second box inside the first, and at the same tier it fills its parent
|
|
54
|
+
* edge to edge so the two have no gap between them.
|
|
55
|
+
*/
|
|
56
|
+
bare: {
|
|
57
|
+
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)";
|
|
58
|
+
true: "bg-transparent shadow-none";
|
|
59
|
+
};
|
|
60
|
+
/** Vertical size. `auto` is for a field that grows with its content (Textarea). */
|
|
61
|
+
height: {
|
|
62
|
+
md: "h-(--era-h-md)";
|
|
63
|
+
full: "h-full";
|
|
64
|
+
auto: "min-h-(--era-h-md)";
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Side padding. `trailing` is for a field that holds a control at its end
|
|
68
|
+
* (the date picker's calendar trigger): the leading edge keeps the shared
|
|
69
|
+
* text inset so the text still lines up with every other field, and the
|
|
70
|
+
* trailing edge becomes the concentric inset for that sm-tier control, so
|
|
71
|
+
* its gap to the field edge equals its gap above and below.
|
|
72
|
+
*/
|
|
73
|
+
pad: {
|
|
74
|
+
even: "px-(--era-field-px)";
|
|
75
|
+
trailing: "ps-(--era-field-px) pe-(--era-sm-inset-md)";
|
|
76
|
+
none: "px-0";
|
|
77
|
+
};
|
|
78
|
+
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
79
|
+
/**
|
|
80
|
+
* `bare` drops the field chrome entirely — no fill, no well, no side
|
|
81
|
+
* padding. For an input nested in a container that is ALREADY the field:
|
|
82
|
+
* a Bar acting as a toolbar, a header row. Without it the input draws a
|
|
83
|
+
* second box inside the first, and at the same tier it fills its parent
|
|
84
|
+
* edge to edge so the two have no gap between them.
|
|
85
|
+
*/
|
|
86
|
+
bare: {
|
|
87
|
+
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)";
|
|
88
|
+
true: "bg-transparent shadow-none";
|
|
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>>;
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
/** Vertical size. `auto` is for a field that grows with its content (Textarea). */
|
|
34
|
+
height: {
|
|
35
|
+
md: 'h-(--era-h-md)',
|
|
36
|
+
// h-full, NOT the md tier: a bare input's container IS the field, so it
|
|
37
|
+
// takes that container's height. Keeping h-md here made a bare input
|
|
38
|
+
// inside an h-md Bar overflow it by the bar's border — a −0.5px gap,
|
|
39
|
+
// which is the even-gap law failing by a hair rather than obviously.
|
|
40
|
+
full: 'h-full',
|
|
41
|
+
auto: 'min-h-(--era-h-md)'
|
|
42
|
+
},
|
|
43
|
+
/**
|
|
44
|
+
* Side padding. `trailing` is for a field that holds a control at its end
|
|
45
|
+
* (the date picker's calendar trigger): the leading edge keeps the shared
|
|
46
|
+
* text inset so the text still lines up with every other field, and the
|
|
47
|
+
* trailing edge becomes the concentric inset for that sm-tier control, so
|
|
48
|
+
* its gap to the field edge equals its gap above and below.
|
|
49
|
+
*/
|
|
50
|
+
pad: {
|
|
51
|
+
even: 'px-(--era-field-px)',
|
|
52
|
+
trailing: 'ps-(--era-field-px) pe-(--era-sm-inset-md)',
|
|
53
|
+
none: 'px-0'
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
compoundVariants: [
|
|
57
|
+
// Chrome and padding travel together: a field with no chrome has no box to
|
|
58
|
+
// pad. Spelling this as a compound rather than leaving it to the caller is
|
|
59
|
+
// what stops `bare` from becoming a second, differently-padded field.
|
|
60
|
+
{ bare: true, class: 'h-full px-0' }
|
|
61
|
+
],
|
|
62
|
+
defaultVariants: { bare: false, height: 'md', pad: 'even' }
|
|
63
|
+
});
|
|
@@ -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}
|