sid-ui 0.0.0-stage → 0.1.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 +54 -2
- package/package.json +51 -4
- package/src/brand/BrandFooter.tsx +45 -0
- package/src/brand/BrandHeading.tsx +24 -0
- package/src/brand/BrandLockup.tsx +47 -0
- package/src/brand/JoinedLogo.tsx +67 -0
- package/src/brand/JoinedMark.tsx +76 -0
- package/src/brand/geometry.ts +26 -0
- package/src/brand/index.ts +11 -0
- package/src/brand/name.ts +5 -0
- package/src/brand/paint.tsx +56 -0
- package/src/components/Action.tsx +44 -0
- package/src/components/AddressSelector.tsx +99 -0
- package/src/components/BarChart.tsx +132 -0
- package/src/components/Calendar.tsx +284 -0
- package/src/components/CalendarAgenda.tsx +87 -0
- package/src/components/CalendarMonth.tsx +223 -0
- package/src/components/CalendarSchedule.tsx +210 -0
- package/src/components/Card.tsx +80 -0
- package/src/components/Charts.tsx +64 -0
- package/src/components/Chat.tsx +35 -0
- package/src/components/CitySelector.tsx +81 -0
- package/src/components/Clock.tsx +196 -0
- package/src/components/CodeInput.tsx +183 -0
- package/src/components/Container.tsx +20 -0
- package/src/components/Content.tsx +50 -0
- package/src/components/Data.tsx +43 -0
- package/src/components/DataInput.tsx +107 -0
- package/src/components/DateField.tsx +162 -0
- package/src/components/DonutChart.tsx +123 -0
- package/src/components/Drawer.tsx +110 -0
- package/src/components/Feedback.tsx +37 -0
- package/src/components/FileUploader.tsx +377 -0
- package/src/components/FunnelChart.tsx +83 -0
- package/src/components/Glyph.tsx +330 -0
- package/src/components/GoogleSignIn.tsx +67 -0
- package/src/components/GridSystem.tsx +131 -0
- package/src/components/HeatmapCalendar.tsx +167 -0
- package/src/components/KanbanBoard.tsx +421 -0
- package/src/components/Kpi.tsx +61 -0
- package/src/components/LayoutPrimitives.tsx +86 -0
- package/src/components/Lists.tsx +19 -0
- package/src/components/LocationSelector.tsx +49 -0
- package/src/components/MessageList.tsx +80 -0
- package/src/components/Nav.tsx +69 -0
- package/src/components/Navigation.tsx +52 -0
- package/src/components/Notification.tsx +115 -0
- package/src/components/NotificationTrigger.tsx +248 -0
- package/src/components/Overlay.tsx +50 -0
- package/src/components/Page.tsx +143 -0
- package/src/components/PageTabs.tsx +28 -0
- package/src/components/PickerShell.tsx +138 -0
- package/src/components/PillNav.tsx +194 -0
- package/src/components/Primitives.tsx +30 -0
- package/src/components/Rating.tsx +78 -0
- package/src/components/Responsive.tsx +128 -0
- package/src/components/ResponsiveFrame.tsx +122 -0
- package/src/components/Segment.tsx +190 -0
- package/src/components/Sparkline.tsx +52 -0
- package/src/components/StateSelector.tsx +39 -0
- package/src/components/Sticky.tsx +95 -0
- package/src/components/Table.tsx +344 -0
- package/src/components/ThemeToggle.tsx +48 -0
- package/src/components/Tile.tsx +23 -0
- package/src/components/TimeColumns.tsx +155 -0
- package/src/components/TimeDial.tsx +260 -0
- package/src/components/TimeField.tsx +196 -0
- package/src/components/TimeInput.tsx +213 -0
- package/src/components/TimeSlots.tsx +83 -0
- package/src/components/Timeline.tsx +111 -0
- package/src/components/TokenDemo.tsx +378 -0
- package/src/components/Tree.tsx +283 -0
- package/src/components/TrendChart.tsx +211 -0
- package/src/components/breakpoints.ts +42 -0
- package/src/components/calendarTypes.ts +26 -0
- package/src/components/chartMath.ts +125 -0
- package/src/components/chartParts.tsx +116 -0
- package/src/components/codeInputRules.ts +58 -0
- package/src/components/date.ts +87 -0
- package/src/components/hooks.ts +53 -0
- package/src/components/index.ts +195 -0
- package/src/components/kanban.ts +78 -0
- package/src/components/motion.ts +78 -0
- package/src/components/places-search.tsx +73 -0
- package/src/components/places.ts +209 -0
- package/src/components/size.ts +3 -0
- package/src/components/time.ts +83 -0
- package/src/components/useFileDrop.ts +104 -0
- package/src/places/geoapify.ts +129 -0
- package/src/styles/astryx.css +5 -0
- package/src/styles/components/brand.css +98 -0
- package/src/styles/components/calendar.css +680 -0
- package/src/styles/components/charts.css +659 -0
- package/src/styles/components/code-input.css +233 -0
- package/src/styles/components/google.css +11 -0
- package/src/styles/components/message-list.css +153 -0
- package/src/styles/components/notification.css +350 -0
- package/src/styles/components/picker.css +114 -0
- package/src/styles/components/pill-nav.css +179 -0
- package/src/styles/components/responsive.css +419 -0
- package/src/styles/components/sticky.css +23 -0
- package/src/styles/components/table.css +267 -0
- package/src/styles/components/time.css +526 -0
- package/src/styles/components/timeline.css +426 -0
- package/src/styles/components/tree.css +269 -0
- package/src/styles/components.css +423 -0
- package/src/styles/joined.css +4 -0
- package/src/styles/theme.css +79 -0
- package/src/styles/tokens.css +338 -0
- package/src/theme/JoinedProvider.tsx +50 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/joined.d.ts +11 -0
- package/src/theme/joined.js +569 -0
- package/src/theme/joined.theme.ts +74 -0
- package/src/theme/joined.variants.d.ts +16 -0
- package/src/theme/theme.css +786 -0
package/README.md
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sid-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React components, tokens, and theme for Joined products. Apps import this package instead of keeping a local copy of the design system.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add sid-ui react react-dom
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`react` and `react-dom` must be 19 or newer. The package also installs `@astryxdesign/core`, `@astryxdesign/theme-neutral`, and `@stylexjs/stylex`.
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Button } from "sid-ui";
|
|
17
|
+
import { JoinedProvider } from "sid-ui/theme";
|
|
18
|
+
|
|
19
|
+
import "sid-ui/styles/joined.css";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
<JoinedProvider>
|
|
24
|
+
<Button variant="primary" label="Save" onClick={onSave} />
|
|
25
|
+
</JoinedProvider>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Next.js apps add `sid-ui` to `transpilePackages`, because the package ships TypeScript source.
|
|
29
|
+
|
|
30
|
+
## Entry points
|
|
31
|
+
|
|
32
|
+
| Import | Contents |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `sid-ui` | Components |
|
|
35
|
+
| `sid-ui/theme` | `JoinedProvider` and the theme object |
|
|
36
|
+
| `sid-ui/styles/joined.css` | Tokens and component styles. Import this once per app. |
|
|
37
|
+
| `sid-ui/places` | Place types and helpers |
|
|
38
|
+
| `sid-ui/geoapify` | Server helper for place search. Pass `process.env.GEOAPIFY_API_KEY`. |
|
|
39
|
+
| `sid-ui/brand-name` | Product name constant, without the component barrel |
|
|
40
|
+
|
|
41
|
+
## Theme source
|
|
42
|
+
|
|
43
|
+
Token values are generated from `src/theme/joined.theme.ts`.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bun run theme:build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Publish
|
|
50
|
+
|
|
51
|
+
From this repo, after `bun pm whoami` shows your registry user:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bun publish
|
|
55
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sid-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"packageManager": "bun@1.4.2",
|
|
5
|
+
"description": "React components, tokens, and theme used by Joined products.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/sid0709/sid-ui.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/sid0709/sid-ui/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/sid0709/sid-ui#readme",
|
|
14
|
+
"keywords": ["react", "design-system", "components", "ui"],
|
|
15
|
+
"sideEffects": ["*.css"],
|
|
16
|
+
"files": ["src", "!src/**/*.test.ts", "README.md"],
|
|
17
|
+
"types": "./src/components/index.ts",
|
|
18
|
+
"main": "./src/components/index.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./src/components/index.ts",
|
|
21
|
+
"./places": "./src/components/places.ts",
|
|
22
|
+
"./geoapify": "./src/places/geoapify.ts",
|
|
23
|
+
"./brand-name": "./src/brand/name.ts",
|
|
24
|
+
"./styles.css": "./src/styles/theme.css",
|
|
25
|
+
"./styles/astryx.css": "./src/styles/astryx.css",
|
|
26
|
+
"./styles/components.css": "./src/styles/components.css",
|
|
27
|
+
"./styles/joined.css": "./src/styles/joined.css",
|
|
28
|
+
"./styles/theme.css": "./src/styles/theme.css",
|
|
29
|
+
"./styles/tokens.css": "./src/styles/tokens.css",
|
|
30
|
+
"./theme": "./src/theme/index.ts"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": ">=19",
|
|
34
|
+
"react-dom": ">=19"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"typecheck": "tsc --noEmit --incremental false -p tsconfig.json",
|
|
38
|
+
"test": "bun test",
|
|
39
|
+
"theme:build": "astryx theme build src/theme/joined.theme.ts -o src/theme/theme.css",
|
|
40
|
+
"prepublishOnly": "bun run typecheck && bun test"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@astryxdesign/core": "0.6.3",
|
|
44
|
+
"@astryxdesign/theme-neutral": "0.6.3",
|
|
45
|
+
"@stylexjs/stylex": "0.19.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@astryxdesign/cli": "0.6.3",
|
|
49
|
+
"@types/react": "19.3.0",
|
|
50
|
+
"@types/react-dom": "19.3.0",
|
|
51
|
+
"typescript": "5.9.3"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { JoinedLogo } from "./JoinedLogo";
|
|
2
|
+
import { JoinedMark } from "./JoinedMark";
|
|
3
|
+
|
|
4
|
+
const MARK_SIZE = "1rem";
|
|
5
|
+
const WORDMARK_HEIGHT = "0.875rem";
|
|
6
|
+
|
|
7
|
+
export interface BrandFooterProps {
|
|
8
|
+
/** Words before the wordmark. */
|
|
9
|
+
lead?: string;
|
|
10
|
+
/** Where the wordmark links, e.g. the Joined home page. Plain text when omitted. */
|
|
11
|
+
href?: string;
|
|
12
|
+
align?: "center" | "start";
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A quiet sign-off under landing, auth, and error pages: the symbol, a lead, and the wordmark. */
|
|
17
|
+
export function BrandFooter({
|
|
18
|
+
lead = "Part of",
|
|
19
|
+
href,
|
|
20
|
+
align = "center",
|
|
21
|
+
className,
|
|
22
|
+
}: BrandFooterProps) {
|
|
23
|
+
const classes = ["os-brand-footer", align === "start" && "os-brand-footer-start", className]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join(" ");
|
|
26
|
+
const signOff = (
|
|
27
|
+
<>
|
|
28
|
+
<JoinedMark variant="blue" size={MARK_SIZE} label="" />
|
|
29
|
+
<span>{lead}</span>
|
|
30
|
+
<JoinedLogo height={WORDMARK_HEIGHT} />
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<footer className={classes}>
|
|
36
|
+
{href ? (
|
|
37
|
+
<a className="os-brand-footer-link" href={href}>
|
|
38
|
+
{signOff}
|
|
39
|
+
</a>
|
|
40
|
+
) : (
|
|
41
|
+
<span className="os-brand-footer-link">{signOff}</span>
|
|
42
|
+
)}
|
|
43
|
+
</footer>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TopNavHeading, type TopNavHeadingProps } from "../components/LayoutPrimitives";
|
|
2
|
+
|
|
3
|
+
import { JoinedLogo } from "./JoinedLogo";
|
|
4
|
+
import { JoinedMark } from "./JoinedMark";
|
|
5
|
+
import { BRAND_NAME } from "./name";
|
|
6
|
+
|
|
7
|
+
export interface BrandHeadingProps extends Omit<
|
|
8
|
+
TopNavHeadingProps,
|
|
9
|
+
"logo" | "logoLabel" | "heading"
|
|
10
|
+
> {
|
|
11
|
+
/** A product under the Joined brand (Scoutwell, Joined Admin…). Omit for Joined itself. */
|
|
12
|
+
product?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The TopNav heading every app uses. Joined itself shows the blue wordmark; a
|
|
17
|
+
* product shows the Joined app icon beside its own name.
|
|
18
|
+
*/
|
|
19
|
+
export function BrandHeading({ product, ...props }: BrandHeadingProps) {
|
|
20
|
+
if (!product || product === BRAND_NAME) {
|
|
21
|
+
return <TopNavHeading {...props} logo={<JoinedLogo />} logoLabel={BRAND_NAME} />;
|
|
22
|
+
}
|
|
23
|
+
return <TopNavHeading {...props} logo={<JoinedMark label="" />} heading={product} />;
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { JoinedLogo } from "./JoinedLogo";
|
|
2
|
+
import { JoinedMark } from "./JoinedMark";
|
|
3
|
+
import { BRAND_NAME } from "./name";
|
|
4
|
+
|
|
5
|
+
/** Rendered heights, so the icon and wordmark keep one proportion everywhere. */
|
|
6
|
+
const ICON_SIZE = "3rem";
|
|
7
|
+
const WORDMARK_HEIGHT = "2.5rem";
|
|
8
|
+
const ENDORSEMENT_HEIGHT = "0.875rem";
|
|
9
|
+
|
|
10
|
+
export interface BrandLockupProps {
|
|
11
|
+
/** A product under the Joined brand. Omit for Joined itself. */
|
|
12
|
+
product?: string;
|
|
13
|
+
/** One line under the logo: what this place is for. */
|
|
14
|
+
tagline?: string;
|
|
15
|
+
align?: "center" | "start";
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The logo block that opens sign-in, sign-up, onboarding, and error pages.
|
|
21
|
+
* Joined shows its wordmark; a product shows the app icon, its name, and "by Joined".
|
|
22
|
+
*/
|
|
23
|
+
export function BrandLockup({ product, tagline, align = "center", className }: BrandLockupProps) {
|
|
24
|
+
const classes = ["os-brand-lockup", align === "start" && "os-brand-lockup-start", className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(" ");
|
|
27
|
+
const isProduct = Boolean(product) && product !== BRAND_NAME;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={classes}>
|
|
31
|
+
{isProduct ? (
|
|
32
|
+
<>
|
|
33
|
+
<div className="os-brand-lockup-row">
|
|
34
|
+
<JoinedMark size={ICON_SIZE} label="" />
|
|
35
|
+
<p className="os-brand-lockup-product">{product}</p>
|
|
36
|
+
</div>
|
|
37
|
+
<span className="os-brand-lockup-endorse">
|
|
38
|
+
by <JoinedLogo height={ENDORSEMENT_HEIGHT} />
|
|
39
|
+
</span>
|
|
40
|
+
</>
|
|
41
|
+
) : (
|
|
42
|
+
<JoinedLogo height={WORDMARK_HEIGHT} />
|
|
43
|
+
)}
|
|
44
|
+
{tagline && <p className="os-brand-lockup-tagline">{tagline}</p>}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { WORDMARK } from "./geometry";
|
|
2
|
+
import { BRAND_NAME } from "./name";
|
|
3
|
+
import {
|
|
4
|
+
BrandGradient,
|
|
5
|
+
labelProps,
|
|
6
|
+
useGradientId,
|
|
7
|
+
solidFill,
|
|
8
|
+
type BrandRamp,
|
|
9
|
+
type BrandSolid,
|
|
10
|
+
} from "./paint";
|
|
11
|
+
|
|
12
|
+
import type { CSSProperties } from "react";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The color treatments shipped in Joined-Logo/1-Brand-Logo, plus `blue`: the
|
|
16
|
+
* blue gradient that brightens in dark mode. Blue is the everyday brand color.
|
|
17
|
+
*/
|
|
18
|
+
export type JoinedLogoVariant =
|
|
19
|
+
"blue" | "blue-gradient" | "meta-blue" | "original" | "black" | "white";
|
|
20
|
+
|
|
21
|
+
export interface JoinedLogoProps {
|
|
22
|
+
/** `blue` everywhere by default; `white` on accent or photo surfaces; `original` for marketing moments. */
|
|
23
|
+
variant?: JoinedLogoVariant;
|
|
24
|
+
/** Rendered height; width follows the artwork's aspect ratio. */
|
|
25
|
+
height?: CSSProperties["height"];
|
|
26
|
+
/** Accessible name. Pass `""` when visible text already names the brand. */
|
|
27
|
+
label?: string;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const RAMPS: Partial<Record<JoinedLogoVariant, BrandRamp>> = {
|
|
32
|
+
blue: "adaptive",
|
|
33
|
+
original: "original",
|
|
34
|
+
"blue-gradient": "blue",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Horizontal, left to right across the wordmark — as in the master files. */
|
|
38
|
+
const VECTOR = { x1: 0, y1: 114.347, x2: 854.843, y2: 114.347 };
|
|
39
|
+
|
|
40
|
+
/** The "Joined" wordmark. */
|
|
41
|
+
export function JoinedLogo({
|
|
42
|
+
variant = "blue",
|
|
43
|
+
height = "1.5rem",
|
|
44
|
+
label = BRAND_NAME,
|
|
45
|
+
className,
|
|
46
|
+
}: JoinedLogoProps) {
|
|
47
|
+
const id = useGradientId();
|
|
48
|
+
const ramp = RAMPS[variant];
|
|
49
|
+
return (
|
|
50
|
+
<svg
|
|
51
|
+
viewBox={`0 0 ${WORDMARK.width} ${WORDMARK.height}`}
|
|
52
|
+
className={className ? `os-brand ${className}` : "os-brand"}
|
|
53
|
+
style={{ height }}
|
|
54
|
+
{...labelProps(label)}
|
|
55
|
+
>
|
|
56
|
+
<path
|
|
57
|
+
d={WORDMARK.d}
|
|
58
|
+
style={ramp ? { fill: `url(#${id})` } : solidFill(variant as BrandSolid)}
|
|
59
|
+
/>
|
|
60
|
+
{ramp && (
|
|
61
|
+
<defs>
|
|
62
|
+
<BrandGradient id={id} ramp={ramp} vector={VECTOR} />
|
|
63
|
+
</defs>
|
|
64
|
+
)}
|
|
65
|
+
</svg>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { APP_TILE, SYMBOL } from "./geometry";
|
|
2
|
+
import { BRAND_NAME } from "./name";
|
|
3
|
+
import { BrandGradient, labelProps, solidFill, useGradientId, type BrandSolid } from "./paint";
|
|
4
|
+
|
|
5
|
+
import type { CSSProperties } from "react";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The treatments shipped in Joined-Logo/3-App-Icon-D-Link, plus `blue`: the bare
|
|
9
|
+
* symbol in the blue gradient that brightens in dark mode.
|
|
10
|
+
*/
|
|
11
|
+
export type JoinedMarkVariant = "app" | "blue" | "gradient" | "meta-blue" | "white";
|
|
12
|
+
|
|
13
|
+
export interface JoinedMarkProps {
|
|
14
|
+
/** `app` is the rounded app tile; the others are the bare symbol. */
|
|
15
|
+
variant?: JoinedMarkVariant;
|
|
16
|
+
/** Rendered height; width follows the artwork's aspect ratio. */
|
|
17
|
+
size?: CSSProperties["height"];
|
|
18
|
+
/** Accessible name. Pass `""` when visible text already names the brand. */
|
|
19
|
+
label?: string;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Diagonal, top-left to bottom-right — as in the master files. */
|
|
24
|
+
const TILE_VECTOR = { x1: 35.1464, y1: 35.1464, x2: 204.854, y2: 204.854 };
|
|
25
|
+
const SYMBOL_VECTOR = { x1: 21.7909, y1: 15.3125, x2: 91.3536, y2: 114.306 };
|
|
26
|
+
|
|
27
|
+
/** The linked-rings symbol: the Joined app icon, or the bare mark for tight spaces. */
|
|
28
|
+
export function JoinedMark({
|
|
29
|
+
variant = "app",
|
|
30
|
+
size = "1.5rem",
|
|
31
|
+
label = BRAND_NAME,
|
|
32
|
+
className,
|
|
33
|
+
}: JoinedMarkProps) {
|
|
34
|
+
const id = useGradientId();
|
|
35
|
+
const svgProps = {
|
|
36
|
+
className: className ? `os-brand ${className}` : "os-brand",
|
|
37
|
+
style: { height: size },
|
|
38
|
+
...labelProps(label),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (variant === "app") {
|
|
42
|
+
return (
|
|
43
|
+
<svg viewBox={`0 0 ${APP_TILE.size} ${APP_TILE.size}`} {...svgProps}>
|
|
44
|
+
<rect
|
|
45
|
+
width={APP_TILE.size}
|
|
46
|
+
height={APP_TILE.size}
|
|
47
|
+
rx={APP_TILE.radius}
|
|
48
|
+
style={{ fill: `url(#${id})` }}
|
|
49
|
+
/>
|
|
50
|
+
<path
|
|
51
|
+
transform={`translate(${APP_TILE.symbolX} ${APP_TILE.symbolY})`}
|
|
52
|
+
d={SYMBOL.d}
|
|
53
|
+
style={solidFill("white")}
|
|
54
|
+
/>
|
|
55
|
+
<defs>
|
|
56
|
+
<BrandGradient id={id} ramp="blue" vector={TILE_VECTOR} />
|
|
57
|
+
</defs>
|
|
58
|
+
</svg>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const ramp = variant === "blue" ? "adaptive" : variant === "gradient" ? "blue" : undefined;
|
|
63
|
+
return (
|
|
64
|
+
<svg viewBox={`0 0 ${SYMBOL.width} ${SYMBOL.height}`} {...svgProps}>
|
|
65
|
+
<path
|
|
66
|
+
d={SYMBOL.d}
|
|
67
|
+
style={ramp ? { fill: `url(#${id})` } : solidFill(variant as BrandSolid)}
|
|
68
|
+
/>
|
|
69
|
+
{ramp && (
|
|
70
|
+
<defs>
|
|
71
|
+
<BrandGradient id={id} ramp={ramp} vector={SYMBOL_VECTOR} />
|
|
72
|
+
</defs>
|
|
73
|
+
)}
|
|
74
|
+
</svg>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Joined logo geometry, copied from the masters in `Joined-Logo/`.
|
|
3
|
+
* Re-copy the `d` attributes if the artwork changes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The "Joined" wordmark (1-Brand-Logo). */
|
|
7
|
+
export const WORDMARK = {
|
|
8
|
+
width: 856,
|
|
9
|
+
height: 230,
|
|
10
|
+
d: "M162.229 145.057C162.229 164.72 155.301 183.755 142.662 198.817C130.023 213.88 112.481 224.008 93.1162 227.423C73.7518 230.837 53.8032 227.32 36.7744 217.488C19.7457 207.657 6.72518 192.139 0 173.662L39.2969 159.359C42.6595 168.598 49.1693 176.357 57.6836 181.272C66.198 186.188 76.1723 187.946 85.8545 186.239C95.5367 184.532 104.308 179.469 110.628 171.938C116.948 164.406 120.411 154.888 120.411 145.057V49.6592H98.1953V10.4541H162.229V145.057ZM452.996 78.4092C462.864 78.4092 472.635 80.3527 481.752 84.1289C490.869 87.9052 499.152 93.4404 506.13 100.418C513.107 107.396 518.643 115.679 522.419 124.796C526.195 133.912 528.139 143.683 528.139 153.551V228.693H486.32V153.551C486.32 149.175 485.458 144.842 483.783 140.799C482.109 136.756 479.654 133.082 476.56 129.987C473.465 126.893 469.792 124.438 465.749 122.764C461.706 121.089 457.372 120.228 452.996 120.228C416.841 120.228 394.769 140.737 366.326 168.511C339.23 194.97 305.761 228.693 250.439 228.693L249.468 228.688C208.416 228.167 175.298 194.726 175.298 153.551C175.298 112.051 208.94 78.4092 250.439 78.4092C290.676 78.4092 323.524 110.034 325.487 149.783C329.273 146.218 333.13 142.477 337.109 138.592C364.206 112.133 397.674 78.4093 452.996 78.4092ZM419.673 228.693H377.854V170.062C392.964 155.465 405.739 144.104 419.673 137.141V228.693ZM611.434 78.5703C627.66 77.5068 643.793 81.7351 657.411 90.6211C668.816 98.0632 677.941 108.436 683.873 120.587L683.89 120.584C688.757 130.537 691.491 141.725 691.491 153.551C691.491 153.601 691.49 153.652 691.49 153.702L591.039 175.228C593.637 178.26 596.775 180.821 600.32 182.767C606.643 186.235 613.92 187.559 621.059 186.54C628.197 185.521 634.813 182.213 639.912 177.114L669.482 206.685C657.984 218.183 643.066 225.64 626.969 227.938C610.871 230.237 594.462 227.251 580.206 219.43C565.95 211.608 554.615 199.374 547.904 184.562C541.193 169.751 539.468 153.162 542.987 137.287C546.507 121.412 555.081 107.107 567.423 96.5195C579.764 85.9321 595.208 79.6339 611.434 78.5703ZM854.843 228.693H813.024V220.918C802.982 225.895 791.669 228.693 779.701 228.693C738.201 228.693 704.559 195.051 704.559 153.551C704.559 112.051 738.201 78.4092 779.701 78.4092C791.668 78.4092 802.982 81.2067 813.024 86.1836V18.2959L854.843 0V228.693ZM250.439 120.228C232.035 120.228 217.115 135.147 217.115 153.551C217.115 171.955 232.035 186.875 250.439 186.875C268.844 186.875 283.764 171.955 283.764 153.551C283.763 135.147 268.844 120.228 250.439 120.228ZM779.701 120.228C761.297 120.228 746.377 135.147 746.377 153.551C746.377 171.955 761.297 186.875 779.701 186.875C798.105 186.875 813.024 171.955 813.024 153.551C813.024 135.147 798.105 120.228 779.701 120.228ZM634.559 125.643C628.519 121.702 621.365 119.827 614.169 120.299C606.973 120.77 600.125 123.564 594.651 128.259C590.401 131.905 587.158 136.545 585.19 141.733L639.927 130.004C638.302 128.377 636.506 126.913 634.559 125.643ZM390.923 84.3574C374.691 92.741 361.015 103.643 349.104 114.474V78.4092H390.923V84.3574ZM370.014 5.88086C384.087 5.88105 395.496 17.2897 395.496 31.3633C395.496 45.437 384.087 56.8465 370.014 56.8467C355.94 56.8467 344.53 45.4371 344.53 31.3633C344.53 17.2896 355.94 5.88086 370.014 5.88086Z",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
/** The linked-rings symbol (3-App-Icon-D-Link). */
|
|
14
|
+
export const SYMBOL = {
|
|
15
|
+
width: 149,
|
|
16
|
+
height: 105,
|
|
17
|
+
d: "M52.2812 0C81.1552 9.07116e-05 104.562 23.4073 104.562 52.2812C104.562 59.6659 103.03 66.6922 100.268 73.0615C99.0509 73.2795 97.798 73.3945 96.5186 73.3945C88.3006 73.3944 81.1799 68.6989 77.6914 61.8447C78.8115 58.87 79.4267 55.6477 79.4268 52.2812C79.4268 37.289 67.2735 25.1349 52.2812 25.1348C37.289 25.1348 25.1348 37.289 25.1348 52.2812C25.1348 64.4893 33.1942 74.8144 44.2822 78.2285C49.3121 88.335 57.1887 96.775 66.8623 102.5C62.2341 103.841 57.3419 104.562 52.2812 104.562C23.4073 104.562 9.07233e-05 81.1552 0 52.2812C0 23.4072 23.4072 0 52.2812 0ZM96.5195 0C125.393 0.000329859 148.801 23.4074 148.801 52.2812C148.801 81.155 125.393 104.562 96.5195 104.562C67.6455 104.562 44.2384 81.1552 44.2383 52.2812C44.2383 44.8961 45.7699 37.8687 48.5322 31.499C49.7488 31.281 51.002 31.168 52.2812 31.168C60.4986 31.1681 67.6196 35.8625 71.1084 42.7158C69.988 45.6909 69.373 48.9143 69.373 52.2812C69.3731 67.2735 81.5273 79.4268 96.5195 79.4268C111.511 79.4264 123.665 67.2733 123.665 52.2812C123.665 40.0733 115.606 29.7473 104.519 26.333C99.4887 16.2267 91.612 7.78552 81.9385 2.06055C86.5664 0.719272 91.4592 0 96.5195 0Z",
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
/** The app tile: a rounded square with the symbol centered on it. */
|
|
21
|
+
export const APP_TILE = {
|
|
22
|
+
size: 240,
|
|
23
|
+
radius: 55.2,
|
|
24
|
+
symbolX: 45.6,
|
|
25
|
+
symbolY: 67.7188,
|
|
26
|
+
} as const;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { BRAND_NAME } from "./name";
|
|
2
|
+
export { JoinedLogo } from "./JoinedLogo";
|
|
3
|
+
export type { JoinedLogoProps, JoinedLogoVariant } from "./JoinedLogo";
|
|
4
|
+
export { JoinedMark } from "./JoinedMark";
|
|
5
|
+
export type { JoinedMarkProps, JoinedMarkVariant } from "./JoinedMark";
|
|
6
|
+
export { BrandHeading } from "./BrandHeading";
|
|
7
|
+
export type { BrandHeadingProps } from "./BrandHeading";
|
|
8
|
+
export { BrandLockup } from "./BrandLockup";
|
|
9
|
+
export type { BrandLockupProps } from "./BrandLockup";
|
|
10
|
+
export { BrandFooter } from "./BrandFooter";
|
|
11
|
+
export type { BrandFooterProps } from "./BrandFooter";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useId, type CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A gradient palette from brand.css: five stops at 0, 25, 50, 75, 100%.
|
|
5
|
+
* `adaptive` is the blue ramp, brightened in dark mode.
|
|
6
|
+
*/
|
|
7
|
+
export type BrandRamp = "adaptive" | "blue" | "original";
|
|
8
|
+
|
|
9
|
+
/** A solid fill from brand.css. */
|
|
10
|
+
export type BrandSolid = "meta-blue" | "black" | "white";
|
|
11
|
+
|
|
12
|
+
/** Start and end of a linear gradient, in the artwork's own coordinates. */
|
|
13
|
+
export interface GradientVector {
|
|
14
|
+
x1: number;
|
|
15
|
+
y1: number;
|
|
16
|
+
x2: number;
|
|
17
|
+
y2: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const STOPS = [1, 2, 3, 4, 5] as const;
|
|
21
|
+
|
|
22
|
+
/** A per-instance gradient id that is safe inside `url(#…)` (React ids may hold `:` or `«»`). */
|
|
23
|
+
export const useGradientId = () => `os-brand-${useId().replace(/[^\w-]/g, "")}`;
|
|
24
|
+
|
|
25
|
+
export const solidFill = (solid: BrandSolid): CSSProperties => ({
|
|
26
|
+
fill: `var(--os-brand-${solid})`,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** A userSpaceOnUse gradient whose stops read the brand ramp from brand.css. */
|
|
30
|
+
export function BrandGradient({
|
|
31
|
+
id,
|
|
32
|
+
ramp,
|
|
33
|
+
vector,
|
|
34
|
+
}: {
|
|
35
|
+
id: string;
|
|
36
|
+
ramp: BrandRamp;
|
|
37
|
+
vector: GradientVector;
|
|
38
|
+
}) {
|
|
39
|
+
return (
|
|
40
|
+
<linearGradient id={id} gradientUnits="userSpaceOnUse" {...vector}>
|
|
41
|
+
{STOPS.map((stop, index) => (
|
|
42
|
+
<stop
|
|
43
|
+
key={stop}
|
|
44
|
+
offset={index / (STOPS.length - 1)}
|
|
45
|
+
style={{ stopColor: `var(--os-brand-${ramp}-${stop})` }}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</linearGradient>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Accessible name for the artwork, or hidden when it sits beside visible text. */
|
|
53
|
+
export const labelProps = (label: string | undefined) =>
|
|
54
|
+
label
|
|
55
|
+
? ({ role: "img", "aria-label": label } as const)
|
|
56
|
+
: ({ "aria-hidden": true, focusable: false } as const);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action components are original Astryx — Joined only themes them.
|
|
3
|
+
* Import them from here so apps depend on sid-ui alone.
|
|
4
|
+
*/
|
|
5
|
+
export { Button } from "@astryxdesign/core/Button";
|
|
6
|
+
export type { ButtonProps, ButtonVariant, ButtonSize } from "@astryxdesign/core/Button";
|
|
7
|
+
|
|
8
|
+
export { IconButton } from "@astryxdesign/core/IconButton";
|
|
9
|
+
export type { IconButtonProps } from "@astryxdesign/core/IconButton";
|
|
10
|
+
|
|
11
|
+
export { ButtonGroup } from "@astryxdesign/core/ButtonGroup";
|
|
12
|
+
export type { ButtonGroupProps, ButtonGroupOrientation } from "@astryxdesign/core/ButtonGroup";
|
|
13
|
+
|
|
14
|
+
export { Link, LinkProvider } from "@astryxdesign/core/Link";
|
|
15
|
+
export type { LinkProps, LinkComponentType } from "@astryxdesign/core/Link";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
DropdownMenu,
|
|
19
|
+
DropdownMenuItem,
|
|
20
|
+
DropdownMenuSubMenu,
|
|
21
|
+
} from "@astryxdesign/core/DropdownMenu";
|
|
22
|
+
export type {
|
|
23
|
+
DropdownMenuProps,
|
|
24
|
+
DropdownMenuItemData,
|
|
25
|
+
DropdownMenuDividerData,
|
|
26
|
+
DropdownMenuSection,
|
|
27
|
+
DropdownMenuOption,
|
|
28
|
+
} from "@astryxdesign/core/DropdownMenu";
|
|
29
|
+
|
|
30
|
+
export { MoreMenu } from "@astryxdesign/core/MoreMenu";
|
|
31
|
+
export type { MoreMenuProps } from "@astryxdesign/core/MoreMenu";
|
|
32
|
+
|
|
33
|
+
export { SegmentedControl, SegmentedControlItem } from "@astryxdesign/core/SegmentedControl";
|
|
34
|
+
export type {
|
|
35
|
+
SegmentedControlProps,
|
|
36
|
+
SegmentedControlItemProps,
|
|
37
|
+
SegmentedControlSize,
|
|
38
|
+
} from "@astryxdesign/core/SegmentedControl";
|
|
39
|
+
|
|
40
|
+
export { ToggleButton, ToggleButtonGroup } from "@astryxdesign/core/ToggleButton";
|
|
41
|
+
export type { ToggleButtonProps, ToggleButtonGroupProps } from "@astryxdesign/core/ToggleButton";
|
|
42
|
+
|
|
43
|
+
export { Toolbar } from "@astryxdesign/core/Toolbar";
|
|
44
|
+
export type { ToolbarProps, ToolbarSize } from "@astryxdesign/core/Toolbar";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
|
|
5
|
+
import { CitySelector } from "./CitySelector";
|
|
6
|
+
import { TextInput, Typeahead } from "./DataInput";
|
|
7
|
+
import { emptyAddress, formatAddress, type Address } from "./places";
|
|
8
|
+
import {
|
|
9
|
+
PLACES_DEBOUNCE_MS,
|
|
10
|
+
PLACES_MIN_QUERY,
|
|
11
|
+
PlacesCredit,
|
|
12
|
+
placeItem,
|
|
13
|
+
placeSearch,
|
|
14
|
+
} from "./places-search";
|
|
15
|
+
import { Stack } from "./Primitives";
|
|
16
|
+
import { StateSelector } from "./StateSelector";
|
|
17
|
+
|
|
18
|
+
/** Suggest real addresses as someone types, then keep street, city, state, and ZIP. */
|
|
19
|
+
export function AddressSelector({
|
|
20
|
+
label,
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
isDisabled,
|
|
24
|
+
}: {
|
|
25
|
+
label: string;
|
|
26
|
+
value: Address;
|
|
27
|
+
onChange: (address: Address) => void;
|
|
28
|
+
isDisabled?: boolean;
|
|
29
|
+
}) {
|
|
30
|
+
const source = useMemo(() => placeSearch("address"), []);
|
|
31
|
+
const countries = useMemo(() => placeSearch("country"), []);
|
|
32
|
+
const formatted = formatAddress(value);
|
|
33
|
+
const set =
|
|
34
|
+
<K extends keyof Address>(key: K) =>
|
|
35
|
+
(next: Address[K]) => {
|
|
36
|
+
onChange({ ...value, [key]: next });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Stack gap={3}>
|
|
41
|
+
<Typeahead
|
|
42
|
+
label={label}
|
|
43
|
+
searchSource={source}
|
|
44
|
+
value={placeItem(formatted, value)}
|
|
45
|
+
onChange={(hit) => onChange(hit?.auxiliaryData ?? emptyAddress())}
|
|
46
|
+
placeholder="Start typing an address"
|
|
47
|
+
minQueryLength={PLACES_MIN_QUERY}
|
|
48
|
+
debounceMs={PLACES_DEBOUNCE_MS}
|
|
49
|
+
emptySearchResultsText="No addresses match."
|
|
50
|
+
isDisabled={isDisabled}
|
|
51
|
+
description="Pick a suggestion to fill the street, city, state, and ZIP."
|
|
52
|
+
/>
|
|
53
|
+
<PlacesCredit />
|
|
54
|
+
<TextInput
|
|
55
|
+
label={`${label} street`}
|
|
56
|
+
value={value.line1}
|
|
57
|
+
onChange={set("line1")}
|
|
58
|
+
placeholder="Street address"
|
|
59
|
+
isDisabled={isDisabled}
|
|
60
|
+
/>
|
|
61
|
+
<CitySelector
|
|
62
|
+
label={`${label} city`}
|
|
63
|
+
value={value.city}
|
|
64
|
+
onChange={set("city")}
|
|
65
|
+
placeholder="City"
|
|
66
|
+
isDisabled={isDisabled}
|
|
67
|
+
/>
|
|
68
|
+
<StateSelector
|
|
69
|
+
label={`${label} state`}
|
|
70
|
+
value={value.state}
|
|
71
|
+
onChange={set("state")}
|
|
72
|
+
isDisabled={isDisabled}
|
|
73
|
+
/>
|
|
74
|
+
<TextInput
|
|
75
|
+
label={`${label} postal code`}
|
|
76
|
+
value={value.postalCode}
|
|
77
|
+
onChange={set("postalCode")}
|
|
78
|
+
placeholder="ZIP code"
|
|
79
|
+
isDisabled={isDisabled}
|
|
80
|
+
/>
|
|
81
|
+
<Typeahead
|
|
82
|
+
label={`${label} country`}
|
|
83
|
+
searchSource={countries}
|
|
84
|
+
value={placeItem(value.country)}
|
|
85
|
+
onChange={(hit) =>
|
|
86
|
+
onChange({
|
|
87
|
+
...value,
|
|
88
|
+
country: hit?.auxiliaryData?.country || hit?.label || "",
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
placeholder="Search countries"
|
|
92
|
+
minQueryLength={PLACES_MIN_QUERY}
|
|
93
|
+
debounceMs={PLACES_DEBOUNCE_MS}
|
|
94
|
+
emptySearchResultsText="No countries match."
|
|
95
|
+
isDisabled={isDisabled}
|
|
96
|
+
/>
|
|
97
|
+
</Stack>
|
|
98
|
+
);
|
|
99
|
+
}
|