@estiva-app/ui 0.12.3 → 0.12.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estiva-app/ui",
3
- "version": "0.12.3",
3
+ "version": "0.12.4",
4
4
  "description": "Estiva's design tokens (the contract) and a small set of primitives (a convenience) for every Estiva app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,6 +30,35 @@ export const Disabled: Story = { args: { variant: 'primary', disabled: true } }
30
30
  export const WithAReason: Story = { args: { variant: 'primary', disabledReason: 'Sign in to add items' } }
31
31
 
32
32
  /** Every variant × size × icon × disabled combination on one canvas. */
33
+ /* The box it is dropped into cannot change its height: a Button states `h-8`
34
+ (or `h-6` when small), and a stretching parent only stretches a child whose
35
+ height is `auto`. The row below says no `items-*` at all, which is the box
36
+ that drew an IconButton 228px tall — this stays 32. */
37
+ export const InATallRow: Story = {
38
+ parameters: { controls: { disable: true } },
39
+ render: () => (
40
+ <div className="flex h-[260px] w-[240px] justify-center rounded-lg border border-border-subtle p-4">
41
+ <Button variant="outlined">Button</Button>
42
+ </div>
43
+ ),
44
+ }
45
+
46
+ /* And the caller says where it sits. This column asks for the left; a Button
47
+ carries no `align-self` of its own, so the left is what it gets. */
48
+ export const InAColumnThatAsksForTheLeft: Story = {
49
+ parameters: { controls: { disable: true } },
50
+ render: () => (
51
+ <div className="flex w-[240px] flex-col items-start gap-2 rounded-lg border border-border-subtle p-4">
52
+ <Button variant="outlined" size="small">
53
+ Copy link
54
+ </Button>
55
+ <Button variant="outlined" size="small">
56
+ Pair with another Folder…
57
+ </Button>
58
+ </div>
59
+ ),
60
+ }
61
+
33
62
  export const AllVariants: Story = {
34
63
  parameters: { controls: { disable: true } },
35
64
  render: () => (
@@ -105,6 +105,24 @@ describe('Button', () => {
105
105
  expect(held).not.toHaveBeenCalled()
106
106
  })
107
107
 
108
+ /* jsdom computes no layout, so the class list is what a test can hold on to.
109
+ Both halves matter: the height is why a stretching parent cannot change
110
+ this button, and the absence of `align-self` is why its caller keeps the
111
+ say on where it sits. The pixels behind both were measured in Chrome on
112
+ 2026-09-12 — 32px in a 260px row either way, and left in a column that
113
+ asks for the left. */
114
+ it('states its own height and no alignment of its own', () => {
115
+ const { rerender } = render(<Button>Default</Button>)
116
+ const defaultButton = screen.getByRole('button', { name: 'Default' })
117
+ expect(defaultButton.className).toContain('h-8')
118
+ expect(defaultButton.className).not.toContain('self-')
119
+
120
+ rerender(<Button size="small">Small</Button>)
121
+ const smallButton = screen.getByRole('button', { name: 'Small' })
122
+ expect(smallButton.className).toContain('h-6')
123
+ expect(smallButton.className).not.toContain('self-')
124
+ })
125
+
108
126
  it('passes native props through', () => {
109
127
  render(
110
128
  <Button form="f1" aria-pressed="true" data-x="y" className="mt-2">
package/src/Button.tsx CHANGED
@@ -66,13 +66,18 @@ export function Button({
66
66
  focusableWhenDisabled={!!disabledReason}
67
67
  className={(state) =>
68
68
  cn(
69
- // `self-center` for the same reason as IconButton's: a flex parent
70
- // with no `items-*` stretches its children, and a button stretched to
71
- // a column's height is not a button. `h-8` and `h-6` below set the
72
- // height, and stretching overrode them. No caller in the suite wants
73
- // a stretched one checked across both apps and this package on
74
- // 2026-09-11, and none passes `w-full` either.
75
- 'inline-flex items-center justify-center gap-1 rounded-md transition-colors font-sans font-medium self-center',
69
+ // No `self-center` here, deliberately `IconButton` needs it and this
70
+ // does not, and the difference is the `h-8` / `h-6` on the next two
71
+ // lines. `align-items: stretch` only stretches a child whose cross
72
+ // size is `auto`; a Button always states its height, so a stretching
73
+ // parent cannot change it. Measured in Chrome on 2026-09-12, in the
74
+ // 260px row of Finding 49: 32px with `self-center` and 32px without.
75
+ // What the class DID do was override the caller on the other axis —
76
+ // in a column it beats the parent's `items-start`, which centred the
77
+ // four actions in Ship's `ProjectRail` and the trigger in five of
78
+ // this package's own story frames, each of which asks for the top.
79
+ // A control does not get to decide where its caller puts it.
80
+ 'inline-flex items-center justify-center gap-1 rounded-md transition-colors font-sans font-medium',
76
81
  size === 'default' && 'h-8 text-btn-default',
77
82
  size === 'small' && 'h-6 text-btn-small',
78
83
  // Extra right padding beside a leading icon, for optical balance.
@@ -54,6 +54,12 @@ export function IconButton({
54
54
  // happened here: a row with no `items-*` drew this 24 wide and 228
55
55
  // tall in Peek's TopicMoreMenu story (Katerina, 2026-09-11). A button
56
56
  // is the size of its icon and its padding, whatever box it lands in.
57
+ //
58
+ // This is the one of the two that needs it: an IconButton states no
59
+ // height, so its cross size is `auto` and a stretching parent takes
60
+ // it. Measured in the same 260px row on 2026-09-12: 24px with this
61
+ // class, 226px without. `Button` states `h-8`/`h-6`, so it cannot be
62
+ // stretched and carries no `self-center` — see the note there.
57
63
  'flex items-center justify-center p-1 rounded-lg transition-colors shrink-0 self-center cursor-pointer',
58
64
  !state.disabled && variant === 'primary' && 'bg-accent-primary hover:bg-accent-hover text-text-inverse',
59
65
  !state.disabled && variant === 'muted' && 'text-text-secondary hover:bg-bg-hover hover:text-text-primary',