@gridui/branding 0.0.0-stage → 0.0.0

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/README.md CHANGED
@@ -1,3 +1,45 @@
1
- # Temporary Holding Version
1
+ # @gridui/branding
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Layout family. `Branding` and `BrandingItem` arrange any number of differently
4
+ proportioned brand items inside a consumer-positioned region. Full-width and
5
+ bottom-corner modes apply within that region. Each incomplete row expands its
6
+ final cell while preserving artwork proportions.
7
+
8
+ The package owns its item geometry and seams. It does not depend on Grid or
9
+ SiteFooter. Consumers own placement in the page, region dimensions, brand assets,
10
+ links, and accessible names. Related component parts share this package; users
11
+ can install its behavior and styles independently of other layout families.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ pnpm add @gridui/branding @gridui/theme
17
+ ```
18
+
19
+ Use compatible React 19 and React DOM 19 in the consuming application.
20
+ Load Theme before this package's stylesheet. No application Tailwind build is
21
+ required.
22
+
23
+ ```tsx
24
+ import { Branding, BrandingItem } from "@gridui/branding";
25
+ import "@gridui/theme/styles.css";
26
+ import "@gridui/branding/styles.css";
27
+
28
+ <Branding placement="bottom-left" itemHeight={48} style={{ minHeight: 160 }}>
29
+ <BrandingItem asChild aspectRatio={1}>
30
+ <a href="/" aria-label="Home"><img src="/symbol.svg" alt="" /></a>
31
+ </BrandingItem>
32
+ <BrandingItem asChild aspectRatio={7.06}>
33
+ <a href="/about"><img src="/wordmark.svg" alt="Our studio" /></a>
34
+ </BrandingItem>
35
+ </Branding>
36
+ ```
37
+
38
+ Read the [branding contract](docs/guides/branding.md) for alignment, container-driven
39
+ wrapping, frame ownership, native links, and hydration behavior.
40
+ [Component reference](docs/reference/README.md) lists the public Props contracts.
41
+ The package-owned [Storybook examples](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/stories/branding.stories.tsx) provide
42
+ adjustable item counts, aspect ratios, cell widths, and placement.
43
+
44
+ See [component ownership](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/docs/component-ownership.md)
45
+ and [release history](https://github.com/sudo-ui/GridUI-React-Monorepo/tree/main/packages-layout/branding/.changeset/history).
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @author WMXPY
3
+ * @package Branding
4
+ * @namespace Branding
5
+ * @description Branding
6
+ */
7
+ import { type ComponentPropsWithRef, type FC } from "react";
8
+ export type BrandingProps = ComponentPropsWithRef<"div"> & {
9
+ readonly placement?: "full" | "bottom-left" | "bottom-right";
10
+ readonly itemHeight?: number;
11
+ readonly frame?: "all" | "internal" | "none";
12
+ };
13
+ export declare const Branding: FC<BrandingProps>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @author WMXPY
3
+ * @package Branding
4
+ * @namespace BrandingItem
5
+ * @description Branding Item
6
+ */
7
+ import type { ComponentPropsWithRef, FC } from "react";
8
+ export type BrandingItemProps = ComponentPropsWithRef<"div"> & {
9
+ readonly asChild?: boolean;
10
+ readonly aspectRatio?: number;
11
+ };
12
+ export declare const BrandingItem: FC<BrandingItemProps>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @author WMXPY
3
+ * @package Branding
4
+ * @namespace Root
5
+ * @description Index
6
+ */
7
+ export type { BrandingProps } from "./branding/branding";
8
+ export { Branding } from "./branding/branding";
9
+ export type { BrandingItemProps } from "./branding-item/branding-item";
10
+ export { BrandingItem } from "./branding-item/branding-item";
package/dist/index.js ADDED
@@ -0,0 +1,138 @@
1
+ // src/branding-item/branding-item.tsx
2
+ import { cn } from "@gridui/style-utils";
3
+ import { Slot } from "@radix-ui/react-slot";
4
+ import { jsx } from "react/jsx-runtime";
5
+ var BrandingItem = (props) => {
6
+ const { asChild = false, aspectRatio = 1, className, style, ...restProps } = props;
7
+ if (!Number.isFinite(aspectRatio) || aspectRatio <= 0) {
8
+ throw new RangeError("BrandingItem aspectRatio must be a positive finite number.");
9
+ }
10
+ const layoutStyle = {
11
+ "--gridui-branding-item-width": `calc(var(--gridui-branding-height, 48px) * ${aspectRatio})`,
12
+ ...style
13
+ };
14
+ const Component = asChild ? Slot : "div";
15
+ return /* @__PURE__ */ jsx(
16
+ Component,
17
+ {
18
+ ...restProps,
19
+ className: cn("gridui-branding-item", className),
20
+ style: layoutStyle
21
+ }
22
+ );
23
+ };
24
+
25
+ // src/branding/branding.tsx
26
+ import { cn as cn2 } from "@gridui/style-utils";
27
+ import { Children, cloneElement, isValidElement, useLayoutEffect, useRef, useState } from "react";
28
+ import { jsx as jsx2 } from "react/jsx-runtime";
29
+ var Branding = (props) => {
30
+ const {
31
+ placement = "bottom-left",
32
+ itemHeight = 48,
33
+ frame = "all",
34
+ children,
35
+ className,
36
+ style,
37
+ ...restProps
38
+ } = props;
39
+ const strip = useRef(null);
40
+ const [
41
+ rows,
42
+ setRows
43
+ ] = useState([]);
44
+ useLayoutEffect(() => {
45
+ const element = strip.current;
46
+ if (!element) {
47
+ return;
48
+ }
49
+ const measureRows = () => {
50
+ let row = 0;
51
+ let previousTop;
52
+ const next = Array.from(element.children, (item) => {
53
+ const top = item.getBoundingClientRect().top;
54
+ if (previousTop !== void 0 && top !== previousTop) {
55
+ row += 1;
56
+ }
57
+ previousTop = top;
58
+ return row;
59
+ });
60
+ setRows((current) => {
61
+ return current.length === next.length && current.every((value, index) => {
62
+ return value === next[index];
63
+ }) ? current : next;
64
+ });
65
+ };
66
+ const observer = new ResizeObserver(measureRows);
67
+ observer.observe(element);
68
+ for (const item of element.children) {
69
+ observer.observe(item);
70
+ }
71
+ measureRows();
72
+ return () => {
73
+ observer.disconnect();
74
+ };
75
+ }, [
76
+ children,
77
+ itemHeight,
78
+ frame
79
+ ]);
80
+ if (!Number.isFinite(itemHeight) || itemHeight <= 0) {
81
+ throw new RangeError("Branding itemHeight must be a positive finite number.");
82
+ }
83
+ const items = Children.toArray(children).map((child) => {
84
+ if (!isValidElement(child) || child.type !== BrandingItem) {
85
+ throw new TypeError("Branding requires direct BrandingItem children.");
86
+ }
87
+ return child;
88
+ });
89
+ const sizes = items.map((item) => {
90
+ const size = itemHeight * (item.props.aspectRatio ?? 1);
91
+ if (!Number.isFinite(size) || size <= 0) {
92
+ throw new RangeError("BrandingItem aspectRatio must be a positive finite number.");
93
+ }
94
+ return size;
95
+ });
96
+ const layoutStyle = {
97
+ "--gridui-branding-height": `${itemHeight}px`,
98
+ "--gridui-branding-width": `${sizes.reduce((sum, size) => {
99
+ return sum + size;
100
+ }, 0)}px`,
101
+ ...style
102
+ };
103
+ return /* @__PURE__ */ jsx2(
104
+ "div",
105
+ {
106
+ ...restProps,
107
+ className: cn2("gridui-branding", className),
108
+ style: layoutStyle,
109
+ "data-placement": placement,
110
+ children: /* @__PURE__ */ jsx2(
111
+ "div",
112
+ {
113
+ ref: strip,
114
+ className: cn2("gridui-branding-strip"),
115
+ "data-frame": frame,
116
+ children: items.map((item, index) => {
117
+ const itemStyle = {
118
+ "--gridui-branding-item-width": `${sizes[index]}px`,
119
+ ...item.props.style
120
+ };
121
+ return cloneElement(item, {
122
+ style: itemStyle,
123
+ ...{
124
+ "data-row-end": rows[index] !== rows[index + 1] || void 0,
125
+ "data-last-row": rows[index] === rows.at(-1) || void 0
126
+ }
127
+ });
128
+ })
129
+ }
130
+ )
131
+ }
132
+ );
133
+ };
134
+ export {
135
+ Branding,
136
+ BrandingItem
137
+ };
138
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/branding-item/branding-item.tsx", "../src/branding/branding.tsx"],
4
+ "sourcesContent": ["/**\n * @author WMXPY\n * @package Branding\n * @namespace BrandingItem\n * @description Branding Item\n */\n\nimport type { LayoutStyle } from \"../shared/types\";\nimport { cn } from \"@gridui/style-utils\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport type { ComponentPropsWithRef, FC } from \"react\";\n\nexport type BrandingItemProps = ComponentPropsWithRef<\"div\"> & {\n readonly asChild?: boolean;\n readonly aspectRatio?: number;\n};\n\nexport const BrandingItem: FC<BrandingItemProps> = (props: BrandingItemProps) => {\n const { asChild = false, aspectRatio = 1, className, style, ...restProps } = props;\n if (!Number.isFinite(aspectRatio) || aspectRatio <= 0) {\n throw new RangeError(\"BrandingItem aspectRatio must be a positive finite number.\");\n }\n const layoutStyle: LayoutStyle = {\n \"--gridui-branding-item-width\": `calc(var(--gridui-branding-height, 48px) * ${aspectRatio})`,\n ...style,\n };\n const Component = asChild ? Slot : \"div\";\n return (<Component\n {...restProps}\n className={cn(\"gridui-branding-item\", className)}\n style={layoutStyle}\n />);\n};\n", "/**\n * @author WMXPY\n * @package Branding\n * @namespace Branding\n * @description Branding\n */\n\nimport { BrandingItem, type BrandingItemProps } from \"../branding-item/branding-item\";\nimport type { LayoutStyle } from \"../shared/types\";\nimport { cn } from \"@gridui/style-utils\";\nimport { Children, cloneElement, type ComponentPropsWithRef, type FC, isValidElement, useLayoutEffect, useRef, useState } from \"react\";\n\nexport type BrandingProps = ComponentPropsWithRef<\"div\"> & {\n readonly placement?: \"full\" | \"bottom-left\" | \"bottom-right\";\n readonly itemHeight?: number;\n readonly frame?: \"all\" | \"internal\" | \"none\";\n};\n\nexport const Branding: FC<BrandingProps> = (props: BrandingProps) => {\n const {\n placement = \"bottom-left\",\n itemHeight = 48,\n frame = \"all\",\n children,\n className,\n style,\n ...restProps\n } = props;\n const strip = useRef<HTMLDivElement>(null);\n const [\n rows,\n setRows,\n ] = useState<readonly number[]>([]);\n useLayoutEffect(() => {\n const element = strip.current;\n if (!element) {\n return;\n }\n const measureRows = (): void => {\n let row = 0;\n let previousTop: number | undefined;\n const next = Array.from(element.children, (item) => {\n const top = item.getBoundingClientRect().top;\n if (previousTop !== undefined && top !== previousTop) {\n row += 1;\n }\n previousTop = top;\n return row;\n });\n setRows(current => {\n return current.length === next.length && current.every((value, index) => {\n return value === next[index];\n }) ? current : next;\n });\n };\n // Native flex layout owns wrapping, including padding, borders and rounding.\n const observer = new ResizeObserver(measureRows);\n observer.observe(element);\n for (const item of element.children) {\n observer.observe(item);\n }\n measureRows();\n return () => {\n observer.disconnect();\n };\n }, [\n children,\n itemHeight,\n frame,\n ]);\n if (!Number.isFinite(itemHeight) || itemHeight <= 0) {\n throw new RangeError(\"Branding itemHeight must be a positive finite number.\");\n }\n const items = Children.toArray(children).map((child) => {\n if (!isValidElement<BrandingItemProps>(child) || child.type !== BrandingItem) {\n throw new TypeError(\"Branding requires direct BrandingItem children.\");\n }\n return child;\n });\n const sizes = items.map((item) => {\n const size = itemHeight * (item.props.aspectRatio ?? 1);\n if (!Number.isFinite(size) || size <= 0) {\n throw new RangeError(\"BrandingItem aspectRatio must be a positive finite number.\");\n }\n return size;\n });\n const layoutStyle: LayoutStyle = {\n \"--gridui-branding-height\": `${itemHeight}px`,\n \"--gridui-branding-width\": `${sizes.reduce((sum, size) => {\n return sum + size;\n }, 0)}px`,\n ...style,\n };\n return (<div\n {...restProps}\n className={cn(\"gridui-branding\", className)}\n style={layoutStyle}\n data-placement={placement}\n >\n <div\n ref={strip}\n className={cn(\"gridui-branding-strip\")}\n data-frame={frame}\n >\n {items.map((item, index) => {\n const itemStyle: LayoutStyle = {\n \"--gridui-branding-item-width\": `${sizes[index]}px`,\n ...item.props.style,\n };\n return cloneElement(item, {\n style: itemStyle,\n ...{\n \"data-row-end\": rows[index] !== rows[index + 1] || undefined,\n \"data-last-row\": rows[index] === rows.at(-1) || undefined,\n },\n });\n })}\n </div>\n </div>);\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["cn", "jsx"]
7
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @author WMXPY
3
+ * @package Branding
4
+ * @namespace Shared
5
+ * @description Types
6
+ */
7
+ import type { CSSProperties } from "react";
8
+ export type LayoutStyle = CSSProperties & {
9
+ [key: `--gridui-${string}`]: string | number;
10
+ };
@@ -0,0 +1,69 @@
1
+ /*! tailwindcss v4.3.5 | MIT License | https://tailwindcss.com */
2
+ @layer theme, base, components, utilities;
3
+ @layer utilities;
4
+ @layer components {
5
+ .gridui-branding {
6
+ display: flex;
7
+ align-items: flex-end;
8
+ justify-content: left;
9
+ width: 100%;
10
+ height: 100%;
11
+ min-width: 0;
12
+ }
13
+ .gridui-branding[data-placement="bottom-right"] {
14
+ justify-content: right;
15
+ }
16
+ .gridui-branding-strip {
17
+ display: flex;
18
+ flex-wrap: wrap;
19
+ box-sizing: border-box;
20
+ width: min(100%, var(--gridui-branding-width));
21
+ min-width: 0;
22
+ }
23
+ .gridui-branding[data-placement="full"] > .gridui-branding-strip {
24
+ width: 100%;
25
+ }
26
+ .gridui-branding-strip[data-frame="all"]:not(:empty) {
27
+ width: min(100%, calc(var(--gridui-branding-width) + 1px));
28
+ border-top: 1px solid var(--gridui-border);
29
+ border-inline-start: 1px solid var(--gridui-border);
30
+ }
31
+ .gridui-branding[data-placement="full"] > .gridui-branding-strip[data-frame="all"] {
32
+ width: 100%;
33
+ }
34
+ .gridui-branding-strip[data-frame="all"] > .gridui-branding-item, .gridui-branding-strip[data-frame="internal"] > .gridui-branding-item {
35
+ border-inline-end: 1px solid var(--gridui-border);
36
+ border-bottom: 1px solid var(--gridui-border);
37
+ }
38
+ .gridui-branding-strip[data-frame="internal"] > .gridui-branding-item[data-row-end] {
39
+ border-inline-end-color: transparent;
40
+ }
41
+ .gridui-branding-strip[data-frame="internal"] > .gridui-branding-item[data-last-row] {
42
+ border-bottom-color: transparent;
43
+ }
44
+ .gridui-branding-item {
45
+ position: relative;
46
+ display: flex;
47
+ flex: 0 0 min(100%, var(--gridui-branding-item-width));
48
+ align-items: center;
49
+ justify-content: center;
50
+ box-sizing: border-box;
51
+ min-width: 0;
52
+ height: var(--gridui-branding-height, 48px);
53
+ padding: var(--gridui-space-8);
54
+ color: inherit;
55
+ overflow-wrap: anywhere;
56
+ text-decoration: none;
57
+ }
58
+ .gridui-branding-item[data-row-end], .gridui-branding-item:last-child {
59
+ flex-grow: 1;
60
+ }
61
+ .gridui-branding-item > :is(img, svg) {
62
+ width: 100%;
63
+ height: 100%;
64
+ object-fit: contain;
65
+ }
66
+ .gridui-branding-item:is(:focus-visible, :has(:focus-visible)) {
67
+ z-index: var(--gridui-z-interaction);
68
+ }
69
+ }
package/docs/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Branding guides
2
+
3
+ Read [Branding cells](guides/branding.md) for the layout and composition contract.
4
+ [Public API reference](reference/README.md) lists the exported component signatures.
5
+
6
+ Branding has its own stylesheet and does not require Grid or SiteFooter. Load
7
+ `@gridui/theme/styles.css` before `@gridui/branding/styles.css`.
@@ -0,0 +1,47 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "name": "@gridui/branding",
4
+ "version": "0.0.0",
5
+ "category": "layout",
6
+ "description": "GridUI React branding cells",
7
+ "usage": "Layout family. `Branding` and `BrandingItem` arrange any number of differently proportioned brand items inside a consumer-positioned region. Full-width and bottom-corner modes apply within that region. Each incomplete row expands its final cell while preserving artwork proportions.",
8
+ "documentation": [
9
+ "README.md",
10
+ "docs/README.md",
11
+ "docs/guides/branding.md",
12
+ "docs/reference/README.md",
13
+ "docs/reference/branding-item.md",
14
+ "docs/reference/branding.md"
15
+ ],
16
+ "exports": [
17
+ {
18
+ "name": "Branding",
19
+ "kind": "component",
20
+ "importPath": "@gridui/branding"
21
+ },
22
+ {
23
+ "name": "BrandingItem",
24
+ "kind": "component",
25
+ "importPath": "@gridui/branding"
26
+ },
27
+ {
28
+ "name": "BrandingItemProps",
29
+ "kind": "type",
30
+ "importPath": "@gridui/branding"
31
+ },
32
+ {
33
+ "name": "BrandingProps",
34
+ "kind": "type",
35
+ "importPath": "@gridui/branding"
36
+ }
37
+ ],
38
+ "styles": [
39
+ "@gridui/theme/styles.css",
40
+ "@gridui/branding/styles.css"
41
+ ],
42
+ "examples": [
43
+ "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/stories/branding.mdx",
44
+ "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/stories/getting-started.mdx",
45
+ "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/stories/branding.stories.tsx"
46
+ ]
47
+ }
@@ -0,0 +1,62 @@
1
+ # Branding cells
2
+
3
+ `Branding` arranges any number of differently proportioned `BrandingItem` cells.
4
+ The consumer places and sizes the branding region. It can accompany a footer but
5
+ has no footer dependency, fixed page position, routes, assets, or required brands.
6
+
7
+ ```tsx
8
+ import { Branding, BrandingItem } from "@gridui/branding";
9
+
10
+ <Branding placement="bottom-left" itemHeight={48} style={{ minHeight: 160 }}>
11
+ <BrandingItem asChild aspectRatio={1}>
12
+ <a href="/" aria-label="Home"><img src="/symbol.svg" alt="" /></a>
13
+ </BrandingItem>
14
+ <BrandingItem asChild aspectRatio={7.06}>
15
+ <a href="/about"><img src="/wordmark.svg" alt="Our studio" /></a>
16
+ </BrandingItem>
17
+ </Branding>
18
+ ```
19
+
20
+ - `placement`: `bottom-left` (default), `bottom-right`, or `full`. Corner modes
21
+ use the items' preferred total width up to the cell width. Full mode uses the
22
+ complete cell width. All three align to the bottom of the allocated region;
23
+ assign a height or min-height when spare vertical space is needed.
24
+ - `itemHeight`: positive finite row height in pixels, default `48`.
25
+ - `BrandingItem aspectRatio`: positive finite preferred cell width / height,
26
+ default `1`. Item width starts at `itemHeight * aspectRatio`. Item padding is
27
+ the shared 8px token. Direct images and SVGs fit within the resulting cell
28
+ without distortion; custom nested content owns its own sizing.
29
+ - `frame`: `all` (default) owns the perimeter and one-pixel seams, `internal`
30
+ owns only seams, and `none` draws no lines.
31
+
32
+ Rows follow native flex wrapping; ResizeObserver identifies the actual row ends,
33
+ including padding, borders, and fractional pixel rounding. Like Grid's
34
+ `fillLastRow`, the final item of **each** incomplete row expands to fill the row;
35
+ other item widths retain their preferred size, subject to their padding and border
36
+ minimum. Oversized items clamp to the row width.
37
+ Artwork retains its proportions when its cell expands or shrinks. No item count
38
+ or common artwork shape is prescribed. Before measurement, CSS flex wrapping
39
+ keeps items within the cell and the final item fills remaining space.
40
+
41
+ Pass direct BrandingItem children, including arrays and conditional children.
42
+ Use `asChild` on items for full-cell native links; supply an accessible name.
43
+ Stable keys retain DOM identity and keyboard focus during reflow. Use conditional
44
+ children to remove items from layout. Empty branding renders no frame. The root
45
+ forwards native div attributes and ref; each item forwards native attributes and
46
+ ref to its div or slotted host. The component does not create a landmark.
47
+
48
+ ## Compose real brand resources
49
+
50
+ The Storybook Two brands and Three brands compositions combine `Logo` from
51
+ `@gridui/gridui-brand`, `SudosaurusWordmark` from `@sudosaurus/sudosaurus-logo`,
52
+ and an optional Lucide `Sparkles` icon. GridUI and Lucide use square artwork;
53
+ Sudosaurus takes the remaining width. The wordmark's consumer-owned flex style
54
+ allows it to shrink to one ordinary cell width before wrapping, preventing larger
55
+ icons from squeezing the wordmark to zero. After wrapping, the final cell fills
56
+ the row while artwork retains its proportions. The preferred aspect ratio determines
57
+ the natural width for corner placement. Its font size follows the available cell
58
+ width and height; proportional letter spacing preserves the wordmark geometry.
59
+
60
+ Import each brand's optional motion stylesheet in the consuming composition.
61
+ These packages are development dependencies for the examples, not runtime
62
+ dependencies of Branding. See the [working composition](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/stories/brand-resources-example.tsx).
@@ -0,0 +1,5 @@
1
+ <!-- Generated by pnpm docs:storybook. -->
2
+ # Component reference
3
+
4
+ - [Branding](branding.md)
5
+ - [BrandingItem](branding-item.md)
@@ -0,0 +1,69 @@
1
+ <!-- Generated from public Props and owned stories. Run pnpm docs:storybook. -->
2
+ # BrandingItem
3
+
4
+ Compose BrandingItem with Branding inside the same @gridui/branding component family.
5
+
6
+ Import from ``@gridui/branding`` and load Theme before this family's stylesheet.
7
+
8
+ ```tsx
9
+ import { BrandingItem } from "@gridui/branding";
10
+ import "@gridui/theme/styles.css";
11
+ import "@gridui/branding/styles.css";
12
+ ```
13
+
14
+ Read the [package contract](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/README.md) for shared behavior and stylesheet dependencies.
15
+
16
+ The [working composition](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/stories/branding.stories.tsx) demonstrates required context, state, and related components. Use View code in Storybook to inspect the actual implementation.
17
+
18
+ ## API
19
+
20
+ The public Props type is the source of these signatures. Defaults below are explicit implementation defaults; story presets are separate. Native attributes remain available through the exported Props type.
21
+
22
+ ### children
23
+
24
+ ``ReactNode`` · Optional
25
+
26
+ Content rendered inside the component.
27
+
28
+ ### onFocus
29
+
30
+ ``(event: FocusEvent<HTMLDivElement, Element>): void`` · Optional
31
+
32
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
33
+
34
+ ### onBlur
35
+
36
+ ``(event: FocusEvent<HTMLDivElement, Element>): void`` · Optional
37
+
38
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
39
+
40
+ ### onChange
41
+
42
+ ``(event: ChangeEvent<HTMLDivElement, Element>): void`` · Optional
43
+
44
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
45
+
46
+ ### onSubmit
47
+
48
+ ``(event: SubmitEvent<HTMLDivElement>): void`` · Optional
49
+
50
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
51
+
52
+ ### onClick
53
+
54
+ ``(event: MouseEvent<HTMLDivElement, MouseEvent>): void`` · Optional
55
+
56
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
57
+
58
+ ### asChild
59
+
60
+ ``boolean | undefined`` · Optional
61
+
62
+ Default: ``false``
63
+
64
+ ### aspectRatio
65
+
66
+ ``number | undefined`` · Optional
67
+
68
+ Default: ``1``
69
+
@@ -0,0 +1,75 @@
1
+ <!-- Generated from public Props and owned stories. Run pnpm docs:storybook. -->
2
+ # Branding
3
+
4
+ Arranges brand items inside a consumer-positioned cell. Choose full width or either bottom corner; each incomplete row extends its final item without stretching its artwork.
5
+
6
+ Import from ``@gridui/branding`` and load Theme before this family's stylesheet.
7
+
8
+ ```tsx
9
+ import { Branding } from "@gridui/branding";
10
+ import "@gridui/theme/styles.css";
11
+ import "@gridui/branding/styles.css";
12
+ ```
13
+
14
+ Read the [package contract](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/README.md) for shared behavior and stylesheet dependencies.
15
+
16
+ The [working composition](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-layout/branding/stories/branding.stories.tsx) demonstrates required context, state, and related components. Use View code in Storybook to inspect the actual implementation.
17
+
18
+ ## API
19
+
20
+ The public Props type is the source of these signatures. Defaults below are explicit implementation defaults; story presets are separate. Native attributes remain available through the exported Props type.
21
+
22
+ ### children
23
+
24
+ ``ReactNode`` · Optional
25
+
26
+ Content rendered inside the component.
27
+
28
+ ### onFocus
29
+
30
+ ``(event: FocusEvent<HTMLDivElement, Element>): void`` · Optional
31
+
32
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
33
+
34
+ ### onBlur
35
+
36
+ ``(event: FocusEvent<HTMLDivElement, Element>): void`` · Optional
37
+
38
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
39
+
40
+ ### onChange
41
+
42
+ ``(event: ChangeEvent<HTMLDivElement, Element>): void`` · Optional
43
+
44
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
45
+
46
+ ### onSubmit
47
+
48
+ ``(event: SubmitEvent<HTMLDivElement>): void`` · Optional
49
+
50
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
51
+
52
+ ### onClick
53
+
54
+ ``(event: MouseEvent<HTMLDivElement, MouseEvent>): void`` · Optional
55
+
56
+ Called when the corresponding interaction occurs; inspect the arguments in Actions.
57
+
58
+ ### placement
59
+
60
+ ``"full" | "bottom-left" | "bottom-right" | undefined`` · Optional
61
+
62
+ Default: ``"bottom-left"``
63
+
64
+ ### itemHeight
65
+
66
+ ``number | undefined`` · Optional
67
+
68
+ Default: ``48``
69
+
70
+ ### frame
71
+
72
+ ``"none" | "all" | "internal" | undefined`` · Optional
73
+
74
+ Default: ``"all"``
75
+
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@gridui/branding",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.0",
4
+ "description": "GridUI React branding cells",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "files": [
8
+ "dist",
9
+ "docs",
10
+ "README.md"
11
+ ],
12
+ "sideEffects": [
13
+ "**/*.css"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./styles.css": "./dist/styles.css"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "dependencies": {
26
+ "@gridui/style-utils": "^4.0.0",
27
+ "@gridui/theme": "^2.5.4",
28
+ "@radix-ui/react-slot": "1.5.0"
29
+ },
30
+ "devDependencies": {
31
+ "@gridui-internal/configuration": "0.0.0-internal",
32
+ "@gridui/gridui-brand": "0.0.0",
33
+ "@jest/globals": "30.5.2",
34
+ "@storybook/react-vite": "10.6.7",
35
+ "@sudosaurus/sudosaurus-logo": "1.0.0",
36
+ "@types/jest": "30.0.0",
37
+ "@types/node": "26.6.6",
38
+ "@types/react": "19.3.0",
39
+ "@types/react-dom": "19.3.0",
40
+ "jest": "30.5.2",
41
+ "lucide-react": "1.71.0",
42
+ "react": "19.3.1",
43
+ "react-dom": "19.3.1",
44
+ "typescript": "6.0.3"
45
+ },
46
+ "peerDependencies": {
47
+ "react": "^19.0.0",
48
+ "react-dom": "^19.0.0"
49
+ },
50
+ "scripts": {
51
+ "build": "node ../../scripts/compilation/package.ts",
52
+ "compile": "tsc --project tsconfig.json",
53
+ "test": "jest",
54
+ "coverage": "jest --coverage"
55
+ }
6
56
  }