@stonedogcode/style 0.9.0 → 0.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonedogcode/style",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "A Panda CSS design system: a themeable Panda preset plus the React components built on it.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "StoneDogCode L.L.C.",
@@ -57,6 +57,28 @@ export interface StyledIconButtonProps extends HTMLStyledProps<"button"> {
57
57
  rel?: string;
58
58
  }
59
59
 
60
+ /**
61
+ * Props this component used to accept and no longer honours (NEH-498).
62
+ *
63
+ * They are dropped rather than forwarded because everything else in `rest` is
64
+ * spread onto the element: left alone, `confirm={true}` reaches the DOM as an
65
+ * invalid attribute and React warns about each one in the consumer's console.
66
+ * The doc above already promised these were ignored — this is what makes that
67
+ * true.
68
+ *
69
+ * This is a migration seam with an end date. Delete it once no consumer passes
70
+ * any of them; none does today, which is why they were removed in the first
71
+ * place.
72
+ */
73
+ const REMOVED_PROPS = [
74
+ "confirm",
75
+ "confirmTitle",
76
+ "confirmBody",
77
+ "onConfirm",
78
+ "loading",
79
+ "noBackground",
80
+ ] as const;
81
+
60
82
  /** Map the full vocabulary onto what the recipe can actually paint. */
61
83
  function toIconVariant(variant: AllowedVariant): IconButtonVariant {
62
84
  if (variant === "unstyled") return "ghost";
@@ -92,6 +114,10 @@ const StyledIconButton = React.forwardRef<HTMLButtonElement, StyledIconButtonPro
92
114
  // ignored.
93
115
  const { zIndex, style, className: incoming, ...restWithoutZIndex } = rest;
94
116
 
117
+ for (const prop of REMOVED_PROPS) {
118
+ delete (restWithoutZIndex as Record<string, unknown>)[prop];
119
+ }
120
+
95
121
  return (
96
122
  <StyledTooltip tooltip={tooltip} placement={placement}>
97
123
  <Element
@@ -32,6 +32,14 @@ import { useResolvedVariant } from "../config/style-config";
32
32
  * radio rather than the group or the selection — reliably the wrong element.
33
33
  * It now points at the group container. Nothing was using it.
34
34
  *
35
+ * ## `onChange` is optional, and its absence means read-only (NEH-498)
36
+ *
37
+ * `checked` is always passed, so the inputs are controlled whether or not a
38
+ * handler is. Without one React reverts every click *and* warns once per radio
39
+ * in the consumer's console — a warning about nothing, which is the kind that
40
+ * teaches people to ignore the real ones. The inputs are marked `readOnly`
41
+ * instead, which is what the group actually is.
42
+ *
35
43
  * The container is a plain `<div>` rather than `StyledBox`, which is what the
36
44
  * original used. `StyledBox` wraps its children in an inner element unless told
37
45
  * not to, and that element would sit between the `radiogroup` and its radios —
@@ -121,7 +129,10 @@ const StyledInputRadio = React.forwardRef<HTMLDivElement, StyledInputRadioProps>
121
129
  value={radio.value}
122
130
  checked={value === radio.value}
123
131
  disabled={radio.disabled}
124
- onChange={onChange}
132
+ // A group with no `onChange` really is read-only — React reverts
133
+ // the click on a controlled input either way — so say so rather
134
+ // than leave React to warn about it in every consumer's console.
135
+ {...(onChange ? { onChange } : { readOnly: true })}
125
136
  className={input}
126
137
  />
127
138
  <div className={control}>
@@ -12,6 +12,33 @@ import { textRecipe } from "styled-system/recipes";
12
12
 
13
13
  const PandaText = styled("span", textRecipe);
14
14
 
15
+ /**
16
+ * The spacing props that only a BLOCK box can honour.
17
+ *
18
+ * Horizontal spacing is deliberately absent. `margin-left` / `margin-right`
19
+ * work perfectly well on an inline box, and inline text inside a sentence is
20
+ * the commonest use of this component — promoting on those would break working
21
+ * layout to fix a different problem.
22
+ */
23
+ const VERTICAL_SPACING_PROPS = [
24
+ "marginTop",
25
+ "marginBottom",
26
+ "marginBlock",
27
+ "marginBlockStart",
28
+ "marginBlockEnd",
29
+ "paddingTop",
30
+ "paddingBottom",
31
+ "paddingBlock",
32
+ "paddingBlockStart",
33
+ "paddingBlockEnd",
34
+ "mt",
35
+ "mb",
36
+ "my",
37
+ "pt",
38
+ "pb",
39
+ "py",
40
+ ] as const;
41
+
15
42
  export interface StyledTextProps
16
43
  extends React.ComponentProps<typeof PandaText> {
17
44
  children?: React.ReactNode | undefined;
@@ -24,9 +51,50 @@ export interface StyledTextProps
24
51
  ellipsis?: boolean | undefined;
25
52
  wrap?: boolean | undefined;
26
53
 
54
+ /**
55
+ * Render as a block box rather than the default inline one.
56
+ *
57
+ * Set this when you want block flow without any spacing prop to trigger it —
58
+ * two paragraphs that should stack, for instance. A vertical margin or
59
+ * padding implies it already; see the note on the component.
60
+ */
61
+ block?: boolean | undefined;
62
+
27
63
  variant?: AllowedTextVariant | undefined;
28
64
  }
29
65
 
66
+ /**
67
+ * `StyledText` renders a `<span>`, which is an INLINE box — and CSS ignores
68
+ * `margin-top` / `margin-bottom` on inline boxes outright.
69
+ *
70
+ * So `<StyledText marginBottom="1">` used to emit the rule, put the class in
71
+ * the DOM, report `margin-bottom: 8px` from `getComputedStyle`, and move
72
+ * nothing. Worse, JSX strips the whitespace between two elements on separate
73
+ * lines, so adjacent `StyledText` siblings rendered as a single run with no
74
+ * space at all — shipped in two products as "No dates to show yetThis does not
75
+ * mean nothing is due" and "OverviewYour Personal Dashboard" (NEH-490).
76
+ *
77
+ * **It worked in some places, which is what made it so hard to see.** Flex
78
+ * items are blockified, so a `StyledText` inside a `StyledStack` becomes a
79
+ * block and its margins apply; the identical component inside a `StyledBox`
80
+ * stays inline and breaks. Whoever adds the prop sees it work in the component
81
+ * they tested.
82
+ *
83
+ * So a vertical spacing prop promotes the box to `display: block`. This cannot
84
+ * break anything that currently works: on an inline box those declarations are
85
+ * already discarded, so nothing can be depending on their effect. The call
86
+ * sites were always right — the component was accepting a prop it could not
87
+ * honour and saying nothing.
88
+ *
89
+ * An explicit `display` from the caller always wins, and `block` is available
90
+ * for the case where you want block flow with no spacing prop to imply it.
91
+ */
92
+ function wantsBlockBox(props: StyledTextProps): boolean {
93
+ if (props.block) return true;
94
+ const bag = props as unknown as Record<string, unknown>;
95
+ return VERTICAL_SPACING_PROPS.some((prop) => bag[prop] !== undefined);
96
+ }
97
+
30
98
  const StyledText = React.forwardRef<HTMLSpanElement, StyledTextProps>((props, ref) => {
31
99
  log.trace("StyledText rendered");
32
100
  const {
@@ -41,6 +109,7 @@ const StyledText = React.forwardRef<HTMLSpanElement, StyledTextProps>((props, re
41
109
  wrap,
42
110
  textAlign,
43
111
  className,
112
+ block: _block,
44
113
  ...rest
45
114
  } = props;
46
115
  const fontSizeProfile = useFontSizeProfile();
@@ -57,6 +126,12 @@ const StyledText = React.forwardRef<HTMLSpanElement, StyledTextProps>((props, re
57
126
  const fontSize = fontSizeMap[finalSize] || fontSizeMap.md;
58
127
 
59
128
  const extraStyles: React.CSSProperties = {};
129
+ // Before `ellipsis`, which sets its own `display: block` and must keep
130
+ // winning — an ellipsised line is block for a different reason and the two
131
+ // agree anyway.
132
+ if (wantsBlockBox(props)) {
133
+ extraStyles.display = "block";
134
+ }
60
135
  if (ellipsis) {
61
136
  extraStyles.textOverflow = "ellipsis";
62
137
  extraStyles.whiteSpace = "nowrap";