@adea-ai/ui 0.137.0 → 0.138.1

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.
@@ -1,5 +1,5 @@
1
1
  import type { ComponentProps } from 'solid-js'
2
- import { splitProps } from 'solid-js'
2
+ import { children, createMemo, splitProps } from 'solid-js'
3
3
  import { cva, type VariantProps } from '#lib/variants'
4
4
  import { cn } from '#lib/utils'
5
5
 
@@ -75,15 +75,35 @@ export const calloutVariants = cva(
75
75
 
76
76
  export type CalloutProps = ComponentProps<'div'> & VariantProps<typeof calloutVariants>
77
77
 
78
+ const isRendered = (node: unknown) =>
79
+ node != null && typeof node !== 'boolean' && (typeof node !== 'string' || node.trim() !== '')
80
+
81
+ const isCalloutTitle = (node: unknown) =>
82
+ typeof node === 'object' &&
83
+ node !== null &&
84
+ 'getAttribute' in node &&
85
+ typeof node.getAttribute === 'function' &&
86
+ node.getAttribute('data-slot') === 'callout-title'
87
+
78
88
  export function Callout(props: CalloutProps) {
79
- const [local, rest] = splitProps(props, ['class', 'variant', 'size'])
89
+ const [local, rest] = splitProps(props, ['class', 'variant', 'size', 'children'])
90
+ const resolved = children(() => local.children)
91
+ // CSS cannot see text nodes, so the callout looks at what it renders and marks a
92
+ // title with nothing after it. The title reads the mark to drop its bottom margin.
93
+ const titleOnly = createMemo(() => {
94
+ const nodes = resolved.toArray().filter(isRendered)
95
+ return nodes.length === 1 && isCalloutTitle(nodes[0])
96
+ })
80
97
  return (
81
98
  <div
82
99
  data-slot="callout"
100
+ data-title-only={titleOnly() ? '' : undefined}
83
101
  role="note"
84
102
  class={cn(calloutVariants({ variant: local.variant, size: local.size }), local.class)}
85
103
  {...rest}
86
- />
104
+ >
105
+ {resolved()}
106
+ </div>
87
107
  )
88
108
  }
89
109
 
@@ -93,22 +113,23 @@ export function Callout(props: CalloutProps) {
93
113
  * level would break it. Pass `role="heading"` with `aria-level` where the
94
114
  * content does belong in the outline.
95
115
  *
96
- * The title always carries a 4px (`mb-1`) margin below it, whatever follows: a
116
+ * The title carries a 4px (`mb-1`) margin below it, whatever follows: a
97
117
  * paragraph, a list, or bare text. CSS cannot see text nodes, so a selector such
98
118
  * as `:last-child` or `:only-child` cannot tell `<Title/>text` (needs the gap)
99
119
  * from `<Title/>` (needs none), and `not-last:mb-1` silently dropped the gap for
100
- * bare text, the most common body. The unconditional margin is the less
101
- * surprising failure: it is never missing where text follows. The price is the
102
- * title-only callout, which would end 4px taller than its padding says. Pass
103
- * `class="mb-0"` there (`cn` lets the caller's margin win); the `WithTitle` story
104
- * shows it.
120
+ * bare text, the most common body. So the gap stays the default and the one case
121
+ * that does not want it is detected by `Callout`, which sees its rendered
122
+ * children: a callout whose only child is a title carries `data-title-only`, and
123
+ * the title drops its margin so the block ends at its own padding. A caller never
124
+ * passes `mb-0` for this. Anything else after the title, text included, keeps the
125
+ * gap.
105
126
  */
106
127
  export function CalloutTitle(props: ComponentProps<'div'>) {
107
128
  const [local, rest] = splitProps(props, ['class'])
108
129
  return (
109
130
  <div
110
131
  data-slot="callout-title"
111
- class={cn('mb-1 font-medium tracking-tight', local.class)}
132
+ class={cn('mb-1 font-medium tracking-tight in-data-title-only:mb-0', local.class)}
112
133
  {...rest}
113
134
  />
114
135
  )