From d59bd9d1f4328506029f6bc765019de8f099d3ad Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 10:02:14 +0000 Subject: [PATCH] View: add center keyword for insetInline and insetBlock Adds insetInline and insetBlock props to View and supports a new "center" value on both, which centers absolutely positioned elements on their axis via 50% inset combined with a translate. Also fixes the zIndex resolver return type missing classNames, which broke the mixin typecheck. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01F6V36sEHRNKzN996awyGmG --- .changeset/view-inset-center.md | 5 + .../reshaped/src/components/View/View.tsx | 4 + .../src/components/View/View.types.ts | 4 + .../components/View/tests/View.stories.tsx | 73 +++++++++++++ .../inset/__snapshots__/index.test.ts.snap | 100 ++++++++++++++++++ .../src/styles/resolvers/inset/index.test.ts | 16 +++ .../src/styles/resolvers/inset/index.ts | 31 +++++- .../src/styles/resolvers/inset/inset.css | 12 --- .../styles/resolvers/inset/inset.module.css | 57 ++++++++++ .../src/styles/resolvers/zIndex/index.ts | 6 +- packages/reshaped/src/styles/types.ts | 5 +- 11 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 .changeset/view-inset-center.md create mode 100644 packages/reshaped/src/styles/resolvers/inset/inset.module.css diff --git a/.changeset/view-inset-center.md b/.changeset/view-inset-center.md new file mode 100644 index 000000000..e7230b86b --- /dev/null +++ b/.changeset/view-inset-center.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +View: Added insetInline and insetBlock props with support for the "center" keyword value for centering absolutely positioned elements diff --git a/packages/reshaped/src/components/View/View.tsx b/packages/reshaped/src/components/View/View.tsx index e9005d936..cc1fedb29 100644 --- a/packages/reshaped/src/components/View/View.tsx +++ b/packages/reshaped/src/components/View/View.tsx @@ -98,6 +98,8 @@ const View = (props: T.Pro insetBottom, insetStart, insetEnd, + insetInline, + insetBlock, zIndex, /** @@ -127,6 +129,8 @@ const View = (props: T.Pro insetBottom, insetStart, insetEnd, + insetInline, + insetBlock, bleed, width, height, diff --git a/packages/reshaped/src/components/View/View.types.ts b/packages/reshaped/src/components/View/View.types.ts index 414c61c1c..bacb475fb 100644 --- a/packages/reshaped/src/components/View/View.types.ts +++ b/packages/reshaped/src/components/View/View.types.ts @@ -111,6 +111,10 @@ export type Props; /** Inset bottom, base unit token number multiplier when used as a number */ insetBottom?: G.Responsive; + /** Inset inline, base unit token number multiplier when used as a number, "center" centers on the inline axis */ + insetInline?: G.Responsive; + /** Inset block, base unit token number multiplier when used as a number, "center" centers on the block axis */ + insetBlock?: G.Responsive; /** z-index style, based on the z-index tokens when used as a string */ zIndex?: TStyles.ZIndex; /** Shadow style, based on the shadow tokens */ diff --git a/packages/reshaped/src/components/View/tests/View.stories.tsx b/packages/reshaped/src/components/View/tests/View.stories.tsx index 0aeb0c630..b834bd358 100644 --- a/packages/reshaped/src/components/View/tests/View.stories.tsx +++ b/packages/reshaped/src/components/View/tests/View.stories.tsx @@ -1005,6 +1005,67 @@ export const inset = { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ), }; diff --git a/packages/reshaped/src/styles/resolvers/inset/__snapshots__/index.test.ts.snap b/packages/reshaped/src/styles/resolvers/inset/__snapshots__/index.test.ts.snap index fdde6499f..b0480e14b 100644 --- a/packages/reshaped/src/styles/resolvers/inset/__snapshots__/index.test.ts.snap +++ b/packages/reshaped/src/styles/resolvers/inset/__snapshots__/index.test.ts.snap @@ -30,22 +30,72 @@ exports[`Styles/Inset > handles undefined value 1`] = `{}`; exports[`Styles/Inset/Block > handles 0 value 1`] = ` { + "classNames": [ + "_root_31d553", + [ + "_--block-unit_31d553", + ], + ], "variables": { "--rs-inset-block-s": 0, }, } `; +exports[`Styles/Inset/Block > handles center value 1`] = ` +{ + "classNames": [ + "_root_31d553", + [ + "_--block-center_31d553", + ], + ], + "variables": { + "--rs-inset-block-s": "center", + }, +} +`; + exports[`Styles/Inset/Block > handles positive value 1`] = ` { + "classNames": [ + "_root_31d553", + [ + "_--block-unit_31d553", + ], + ], "variables": { "--rs-inset-block-s": 4, }, } `; +exports[`Styles/Inset/Block > handles responsive center value 1`] = ` +{ + "classNames": [ + "_root_31d553", + [ + "_--block-center_31d553", + "_--block-unit--m_31d553", + ], + ], + "variables": { + "--rs-inset-block-m": 4, + "--rs-inset-block-s": "center", + }, +} +`; + exports[`Styles/Inset/Block > handles responsive value 1`] = ` { + "classNames": [ + "_root_31d553", + [ + "_--block-unit_31d553", + "_--block-unit--m_31d553", + "_--block-unit--l_31d553", + ], + ], "variables": { "--rs-inset-block-l": 2, "--rs-inset-block-m": 0, @@ -114,22 +164,72 @@ exports[`Styles/Inset/End > handles undefined value 1`] = `{}`; exports[`Styles/Inset/Inline > handles 0 value 1`] = ` { + "classNames": [ + "_root_31d553", + [ + "_--inline-unit_31d553", + ], + ], "variables": { "--rs-inset-inline-s": 0, }, } `; +exports[`Styles/Inset/Inline > handles center value 1`] = ` +{ + "classNames": [ + "_root_31d553", + [ + "_--inline-center_31d553", + ], + ], + "variables": { + "--rs-inset-inline-s": "center", + }, +} +`; + exports[`Styles/Inset/Inline > handles positive value 1`] = ` { + "classNames": [ + "_root_31d553", + [ + "_--inline-unit_31d553", + ], + ], "variables": { "--rs-inset-inline-s": 4, }, } `; +exports[`Styles/Inset/Inline > handles responsive center value 1`] = ` +{ + "classNames": [ + "_root_31d553", + [ + "_--inline-center_31d553", + "_--inline-unit--m_31d553", + ], + ], + "variables": { + "--rs-inset-inline-m": 4, + "--rs-inset-inline-s": "center", + }, +} +`; + exports[`Styles/Inset/Inline > handles responsive value 1`] = ` { + "classNames": [ + "_root_31d553", + [ + "_--inline-unit_31d553", + "_--inline-unit--m_31d553", + "_--inline-unit--l_31d553", + ], + ], "variables": { "--rs-inset-inline-l": 2, "--rs-inset-inline-m": 0, diff --git a/packages/reshaped/src/styles/resolvers/inset/index.test.ts b/packages/reshaped/src/styles/resolvers/inset/index.test.ts index a980aefcc..b72691662 100644 --- a/packages/reshaped/src/styles/resolvers/inset/index.test.ts +++ b/packages/reshaped/src/styles/resolvers/inset/index.test.ts @@ -112,9 +112,17 @@ describe("Styles/Inset/Inline", () => { expect(insetInline()).toMatchSnapshot(); }); + test("handles center value", () => { + expect(insetInline("center")).toMatchSnapshot(); + }); + test("handles responsive value", async () => { expect(insetInline({ s: 4, m: 0, l: 2 })).toMatchSnapshot(); }); + + test("handles responsive center value", async () => { + expect(insetInline({ s: "center", m: 4 })).toMatchSnapshot(); + }); }); describe("Styles/Inset/Block", () => { @@ -130,7 +138,15 @@ describe("Styles/Inset/Block", () => { expect(insetBlock()).toMatchSnapshot(); }); + test("handles center value", () => { + expect(insetBlock("center")).toMatchSnapshot(); + }); + test("handles responsive value", async () => { expect(insetBlock({ s: 4, m: 0, l: 2 })).toMatchSnapshot(); }); + + test("handles responsive center value", async () => { + expect(insetBlock({ s: "center", m: 4 })).toMatchSnapshot(); + }); }); diff --git a/packages/reshaped/src/styles/resolvers/inset/index.ts b/packages/reshaped/src/styles/resolvers/inset/index.ts index 1be493b32..96632e012 100644 --- a/packages/reshaped/src/styles/resolvers/inset/index.ts +++ b/packages/reshaped/src/styles/resolvers/inset/index.ts @@ -1,5 +1,6 @@ -import { responsiveVariables } from "@/utilities/props"; +import { responsiveClassNames, responsiveVariables } from "@/utilities/props"; import * as T from "@/styles/types"; +import s from "./inset.module.css"; import "./inset.css"; const inset: T.StyleResolver = (value) => { @@ -27,14 +28,34 @@ export const insetEnd: T.StyleResolver = (value) => { return { variables: responsiveVariables("--rs-inset-end", value) }; }; -export const insetInline: T.StyleResolver = (value) => { +export const insetInline: T.StyleResolver = (value) => { if (value === undefined) return {}; - return { variables: responsiveVariables("--rs-inset-inline", value) }; + const classNames = responsiveClassNames( + s, + (value) => (value === "center" ? "--inline-center" : "--inline-unit"), + value, + { excludeValueFromClassName: true } + ); + + return { + classNames: [s.root, classNames], + variables: responsiveVariables("--rs-inset-inline", value), + }; }; -export const insetBlock: T.StyleResolver = (value) => { +export const insetBlock: T.StyleResolver = (value) => { if (value === undefined) return {}; - return { variables: responsiveVariables("--rs-inset-block", value) }; + const classNames = responsiveClassNames( + s, + (value) => (value === "center" ? "--block-center" : "--block-unit"), + value, + { excludeValueFromClassName: true } + ); + + return { + classNames: [s.root, classNames], + variables: responsiveVariables("--rs-inset-block", value), + }; }; export default inset; diff --git a/packages/reshaped/src/styles/resolvers/inset/inset.css b/packages/reshaped/src/styles/resolvers/inset/inset.css index 64da175bd..896a5e3ff 100644 --- a/packages/reshaped/src/styles/resolvers/inset/inset.css +++ b/packages/reshaped/src/styles/resolvers/inset/inset.css @@ -4,18 +4,6 @@ inset: calc(var(--rs-inset) * var(--rs-unit-x1)); } -@responsive [style*="--rs-inset-inline-"] { - @variable --rs-inset-inline 0; - - inset-inline: calc(var(--rs-inset-inline) * var(--rs-unit-x1)); -} - -@responsive [style*="--rs-inset-block-"] { - @variable --rs-inset-block 0; - - inset-block: calc(var(--rs-inset-block) * var(--rs-unit-x1)); -} - @responsive [style*="--rs-inset-end-"] { @variable --rs-inset-end 0; diff --git a/packages/reshaped/src/styles/resolvers/inset/inset.module.css b/packages/reshaped/src/styles/resolvers/inset/inset.module.css new file mode 100644 index 000000000..e5aadf6ed --- /dev/null +++ b/packages/reshaped/src/styles/resolvers/inset/inset.module.css @@ -0,0 +1,57 @@ +/* stylelint-disable plugin/browser-compat -- graceful degradation, var() fallbacks keep centering working without @property */ +@property --rs-inset-translate-x { + syntax: ""; + initial-value: 0; + inherits: false; +} + +@property --rs-inset-translate-y { + syntax: ""; + initial-value: 0; + inherits: false; +} +/* stylelint-enable plugin/browser-compat */ + +@responsive .root[style*="--rs-inset-inline-"] { + @variable --rs-inset-inline 0; +} + +@responsive .root[style*="--rs-inset-block-"] { + @variable --rs-inset-block 0; +} + +@responsive .--inline { + @value center { + /* Physical properties keep centering direction-agnostic in RTL */ + left: 50%; + right: auto; + + --rs-inset-translate-x: -50%; + + translate: var(--rs-inset-translate-x, 0) var(--rs-inset-translate-y, 0); + } + + @value unit { + --rs-inset-translate-x: 0; + + inset-inline: calc(var(--rs-inset-inline) * var(--rs-unit-x1)); + } +} + +@responsive .--block { + @value center { + /* Physical properties keep centering direction-agnostic in RTL */ + top: 50%; + bottom: auto; + + --rs-inset-translate-y: -50%; + + translate: var(--rs-inset-translate-x, 0) var(--rs-inset-translate-y, 0); + } + + @value unit { + --rs-inset-translate-y: 0; + + inset-block: calc(var(--rs-inset-block) * var(--rs-unit-x1)); + } +} diff --git a/packages/reshaped/src/styles/resolvers/zIndex/index.ts b/packages/reshaped/src/styles/resolvers/zIndex/index.ts index 06860d9e4..d31911e79 100644 --- a/packages/reshaped/src/styles/resolvers/zIndex/index.ts +++ b/packages/reshaped/src/styles/resolvers/zIndex/index.ts @@ -1,9 +1,13 @@ import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; import * as T from "@/styles/types"; import "./zIndex.css"; -const zIndex: (value?: T.ZIndex) => { variables?: React.CSSProperties } = (value) => { +const zIndex: (value?: T.ZIndex) => { + variables?: React.CSSProperties; + classNames?: ClassName; +} = (value) => { if (value === undefined) return {}; return { diff --git a/packages/reshaped/src/styles/types.ts b/packages/reshaped/src/styles/types.ts index e5ffa2dc5..133e92c46 100644 --- a/packages/reshaped/src/styles/types.ts +++ b/packages/reshaped/src/styles/types.ts @@ -11,6 +11,7 @@ type Size = Literal | Unit; export type Margin = Unit; export type Padding = Unit; export type Inset = Unit | "auto"; +export type InsetAxis = Inset | "center"; export type AspectRatio = number; export type Bleed = Unit; @@ -94,8 +95,8 @@ export type Mixin = { insetBottom?: G.Responsive; insetStart?: G.Responsive; insetEnd?: G.Responsive; - insetInline?: G.Responsive; - insetBlock?: G.Responsive; + insetInline?: G.Responsive; + insetBlock?: G.Responsive; padding?: G.Responsive; paddingTop?: G.Responsive;