@gridui/branding 0.0.0-stage → 1.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 +44 -2
- package/dist/branding/branding.d.ts +13 -0
- package/dist/branding-item/branding-item.d.ts +12 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +7 -0
- package/dist/shared/types.d.ts +10 -0
- package/dist/styles.css +69 -0
- package/docs/README.md +7 -0
- package/docs/catalog.json +47 -0
- package/docs/guides/branding.md +62 -0
- package/docs/reference/README.md +5 -0
- package/docs/reference/branding-item.md +69 -0
- package/docs/reference/branding.md +75 -0
- package/package.json +53 -3
package/README.md
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gridui/branding
|
|
2
2
|
|
|
3
|
-
|
|
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>;
|
package/dist/index.d.ts
ADDED
|
@@ -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": ";AAQA,SAAS,UAAU;AACnB,SAAS,YAAY;AAkBT;AAVL,IAAM,eAAsC,CAAC,UAA6B;AAC7E,QAAM,EAAE,UAAU,OAAO,cAAc,GAAG,WAAW,OAAO,GAAG,UAAU,IAAI;AAC7E,MAAI,CAAC,OAAO,SAAS,WAAW,KAAK,eAAe,GAAG;AACnD,UAAM,IAAI,WAAW,4DAA4D;AAAA,EACrF;AACA,QAAM,cAA2B;AAAA,IAC7B,gCAAgC,8CAA8C,WAAW;AAAA,IACzF,GAAG;AAAA,EACP;AACA,QAAM,YAAY,UAAU,OAAO;AACnC,SAAQ;AAAA,IAAC;AAAA;AAAA,MACJ,GAAG;AAAA,MACJ,WAAW,GAAG,wBAAwB,SAAS;AAAA,MAC/C,OAAO;AAAA;AAAA,EACX;AACJ;;;ACvBA,SAAS,MAAAA,WAAU;AACnB,SAAS,UAAU,cAAmD,gBAAgB,iBAAiB,QAAQ,gBAAgB;AAyFvH,gBAAAC,YAAA;AAjFD,IAAM,WAA8B,CAAC,UAAyB;AACjE,QAAM;AAAA,IACF,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACP,IAAI;AACJ,QAAM,QAAQ,OAAuB,IAAI;AACzC,QAAM;AAAA,IACF;AAAA,IACA;AAAA,EACJ,IAAI,SAA4B,CAAC,CAAC;AAClC,kBAAgB,MAAM;AAClB,UAAM,UAAU,MAAM;AACtB,QAAI,CAAC,SAAS;AACV;AAAA,IACJ;AACA,UAAM,cAAc,MAAY;AAC5B,UAAI,MAAM;AACV,UAAI;AACJ,YAAM,OAAO,MAAM,KAAK,QAAQ,UAAU,CAAC,SAAS;AAChD,cAAM,MAAM,KAAK,sBAAsB,EAAE;AACzC,YAAI,gBAAgB,UAAa,QAAQ,aAAa;AAClD,iBAAO;AAAA,QACX;AACA,sBAAc;AACd,eAAO;AAAA,MACX,CAAC;AACD,cAAQ,aAAW;AACf,eAAO,QAAQ,WAAW,KAAK,UAAU,QAAQ,MAAM,CAAC,OAAO,UAAU;AACrE,iBAAO,UAAU,KAAK,KAAK;AAAA,QAC/B,CAAC,IAAI,UAAU;AAAA,MACnB,CAAC;AAAA,IACL;AAEA,UAAM,WAAW,IAAI,eAAe,WAAW;AAC/C,aAAS,QAAQ,OAAO;AACxB,eAAW,QAAQ,QAAQ,UAAU;AACjC,eAAS,QAAQ,IAAI;AAAA,IACzB;AACA,gBAAY;AACZ,WAAO,MAAM;AACT,eAAS,WAAW;AAAA,IACxB;AAAA,EACJ,GAAG;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACJ,CAAC;AACD,MAAI,CAAC,OAAO,SAAS,UAAU,KAAK,cAAc,GAAG;AACjD,UAAM,IAAI,WAAW,uDAAuD;AAAA,EAChF;AACA,QAAM,QAAQ,SAAS,QAAQ,QAAQ,EAAE,IAAI,CAAC,UAAU;AACpD,QAAI,CAAC,eAAkC,KAAK,KAAK,MAAM,SAAS,cAAc;AAC1E,YAAM,IAAI,UAAU,iDAAiD;AAAA,IACzE;AACA,WAAO;AAAA,EACX,CAAC;AACD,QAAM,QAAQ,MAAM,IAAI,CAAC,SAAS;AAC9B,UAAM,OAAO,cAAc,KAAK,MAAM,eAAe;AACrD,QAAI,CAAC,OAAO,SAAS,IAAI,KAAK,QAAQ,GAAG;AACrC,YAAM,IAAI,WAAW,4DAA4D;AAAA,IACrF;AACA,WAAO;AAAA,EACX,CAAC;AACD,QAAM,cAA2B;AAAA,IAC7B,4BAA4B,GAAG,UAAU;AAAA,IACzC,2BAA2B,GAAG,MAAM,OAAO,CAAC,KAAK,SAAS;AACtD,aAAO,MAAM;AAAA,IACjB,GAAG,CAAC,CAAC;AAAA,IACL,GAAG;AAAA,EACP;AACA,SAAQ,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACJ,GAAG;AAAA,MACJ,WAAWD,IAAG,mBAAmB,SAAS;AAAA,MAC1C,OAAO;AAAA,MACP,kBAAgB;AAAA,MAEhB,0BAAAC;AAAA,QAAC;AAAA;AAAA,UACG,KAAK;AAAA,UACL,WAAWD,IAAG,uBAAuB;AAAA,UACrC,cAAY;AAAA,UAEX,gBAAM,IAAI,CAAC,MAAM,UAAU;AACxB,kBAAM,YAAyB;AAAA,cAC3B,gCAAgC,GAAG,MAAM,KAAK,CAAC;AAAA,cAC/C,GAAG,KAAK,MAAM;AAAA,YAClB;AACA,mBAAO,aAAa,MAAM;AAAA,cACtB,OAAO;AAAA,cACP,GAAG;AAAA,gBACC,gBAAgB,KAAK,KAAK,MAAM,KAAK,QAAQ,CAAC,KAAK;AAAA,gBACnD,iBAAiB,KAAK,KAAK,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,cACpD;AAAA,YACJ,CAAC;AAAA,UACL,CAAC;AAAA;AAAA,MACL;AAAA;AAAA,EACJ;AACJ;",
|
|
6
|
+
"names": ["cn", "jsx"]
|
|
7
|
+
}
|
package/dist/styles.css
ADDED
|
@@ -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": "1.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,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": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.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": "1.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
|
}
|