@freightos/freightwind 2.1.2 → 2.1.4
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/LICENSE +21 -21
- package/README.md +28 -28
- package/dist/cjs/components/chip.js +1 -1
- package/dist/cjs/components/upload.js +321 -0
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/lib/file-extensions.js +678 -0
- package/dist/cjs/lib/use-stable-id.js +2 -3
- package/dist/esm/components/chip.js +1 -1
- package/dist/esm/components/upload.js +318 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/lib/file-extensions.js +671 -0
- package/dist/esm/lib/use-stable-id.js +2 -3
- package/dist/types/components/chip.d.ts +1 -1
- package/dist/types/components/upload.d.ts +66 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/file-extensions.d.ts +52 -0
- package/guidelines/Guidelines.md +233 -233
- package/guidelines/design-tokens/colors.md +81 -81
- package/guidelines/design-tokens/spacing.md +45 -45
- package/guidelines/design-tokens/typography.md +87 -87
- package/guidelines/overview-components.md +252 -252
- package/guidelines/overview-icons.md +52 -52
- package/package.json +104 -102
- package/tokens.css +419 -419
|
@@ -10,7 +10,7 @@ export interface ChipProps {
|
|
|
10
10
|
children?: string;
|
|
11
11
|
}
|
|
12
12
|
declare const chipVariants: (props?: ({
|
|
13
|
-
variant?: "default" | "
|
|
13
|
+
variant?: "default" | "success" | "error" | "info" | "warning" | "neutral" | "highlight" | "notice" | null | undefined;
|
|
14
14
|
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
15
15
|
declare function Chip({ variant, icon, closable, onClose, badge, children, }: ChipProps): import("react/jsx-runtime").JSX.Element | null;
|
|
16
16
|
export { Chip, chipVariants };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
export type UploadVariant = 'rectangle' | 'square';
|
|
4
|
+
export type ForbiddenReason = 'multiple' | 'type' | 'size' | 'extension' | 'custom';
|
|
5
|
+
export type UploadFile = File | {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
export type UploadTranslator = (key: string) => string;
|
|
9
|
+
declare const uploadVariants: (props?: ({
|
|
10
|
+
variant?: "square" | "rectangle" | null | undefined;
|
|
11
|
+
state?: "default" | "disabled" | "forbidden" | "uploading" | "uploading-spinner" | "success" | "error" | null | undefined;
|
|
12
|
+
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
13
|
+
export interface UploadProps extends Omit<HTMLAttributes<HTMLLabelElement>, 'onChange'>, VariantProps<typeof uploadVariants> {
|
|
14
|
+
/** Test automation identifier. Rendered as `data-test-id` on the root and propagated to inner DOM nodes. */
|
|
15
|
+
dataTestId: string;
|
|
16
|
+
/** Layout variant. */
|
|
17
|
+
variant?: UploadVariant;
|
|
18
|
+
/** Single associated file (controlled). Pass `{ name: string }` for SSR display-only previews. */
|
|
19
|
+
value?: UploadFile | null;
|
|
20
|
+
/** Multiple associated files (controlled). Overrides `value` when provided. */
|
|
21
|
+
files?: UploadFile[];
|
|
22
|
+
/** Max number of files allowed. Omit or set to 1 for single-file mode. Exceeding this → forbidden. */
|
|
23
|
+
maxFiles?: number;
|
|
24
|
+
/** MIME types / extensions accepted. Pass an array, e.g. `['application/pdf', '.png']`. */
|
|
25
|
+
accept?: string[];
|
|
26
|
+
/** Max bytes per file. Exceeding this → forbidden. */
|
|
27
|
+
maxSize?: number;
|
|
28
|
+
/** Drives the error visual state (red frame + red icon). */
|
|
29
|
+
isError?: boolean;
|
|
30
|
+
/** Disables the surface (gray border, no interaction, no remove allowed). */
|
|
31
|
+
isDisabled?: boolean;
|
|
32
|
+
/** Stretch the surface to fill its parent's width and height. */
|
|
33
|
+
isFillContainer?: boolean;
|
|
34
|
+
/** Override copy per state. Pass any subset; falls back to defaults. */
|
|
35
|
+
labels?: Partial<{
|
|
36
|
+
idleTitle: string;
|
|
37
|
+
idleSubtitle: string;
|
|
38
|
+
idleCompact: string;
|
|
39
|
+
uploading: string;
|
|
40
|
+
forbidden: Partial<Record<ForbiddenReason, {
|
|
41
|
+
title: string;
|
|
42
|
+
subtitle: string;
|
|
43
|
+
compact?: string;
|
|
44
|
+
}>>;
|
|
45
|
+
}>;
|
|
46
|
+
/** Optional translation function for all built-in copy. Defaults to identity. */
|
|
47
|
+
t?: UploadTranslator;
|
|
48
|
+
/** Fires when one or more files are selected or dropped. The component flips to `uploading` after this fires; call `setIsUploading(false)` when your async upload completes. Pass an optional `progress` value (0–1) to drive the determinate progress bar; omit it for the indeterminate animation. */
|
|
49
|
+
onUpload?: (files: File[], setIsUploading: (v: boolean, progress?: number) => void) => void;
|
|
50
|
+
/** Fires when the trash button is clicked. Removes ALL files. */
|
|
51
|
+
onRemove?: () => void;
|
|
52
|
+
/** Fires when an invalid drag/drop is detected. */
|
|
53
|
+
onForbidden?: (reason: ForbiddenReason) => void;
|
|
54
|
+
/** A11y label override for the surface. */
|
|
55
|
+
ariaLabel?: string;
|
|
56
|
+
/** Seed the internal uploading state on mount. Useful for static demos and Storybook. */
|
|
57
|
+
defaultUploading?: boolean;
|
|
58
|
+
/** Seed the initial progress value (0–1) when defaultUploading is true. Disables the indeterminate animation and shows a static determinate bar. */
|
|
59
|
+
defaultUploadProgress?: number;
|
|
60
|
+
/** Seed the forbidden state on mount with a specific reason. Useful for static demos and Storybook. */
|
|
61
|
+
defaultForbidden?: ForbiddenReason;
|
|
62
|
+
/** Controls the uploading display: 'progress' = text + progress bar (default); 'spinner' = 32×32 spinner only (no text — enforced for square too). */
|
|
63
|
+
uploadingStyle?: 'progress' | 'spinner';
|
|
64
|
+
}
|
|
65
|
+
export declare const Upload: import("react").ForwardRefExoticComponent<UploadProps & import("react").RefAttributes<HTMLLabelElement>>;
|
|
66
|
+
export { uploadVariants };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ export { cn } from './lib/utils';
|
|
|
2
2
|
export { useStableId } from './lib/use-stable-id';
|
|
3
3
|
export { iconMap, renderInputIcon } from './lib/icon-utils';
|
|
4
4
|
export type { IconName, InputShellSize } from './lib/icon-utils';
|
|
5
|
+
export { Upload, uploadVariants } from './components/upload';
|
|
6
|
+
export type { UploadProps, UploadVariant, UploadFile, ForbiddenReason, UploadTranslator } from './components/upload';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIME-type → extensions map sourced from the IANA / Apache / Nginx MIME database
|
|
3
|
+
* (https://github.com/jshttp/mime-db). Keys are canonical MIME types
|
|
4
|
+
* (e.g. `image/aces`); values are the extensions that legitimately produce that
|
|
5
|
+
* type, without a leading dot and in lowercase.
|
|
6
|
+
*
|
|
7
|
+
* Used to validate uploaded files: reject when the browser-reported `File.type`
|
|
8
|
+
* is unknown, **and** cross-check that the filename's extension matches the set
|
|
9
|
+
* registered for that type. This catches "extension spoofing" — e.g. a `.exe`
|
|
10
|
+
* renamed to `.jpg` will report `application/x-msdownload` (or empty), which
|
|
11
|
+
* does not list `jpg`, so it fails validation.
|
|
12
|
+
*/
|
|
13
|
+
export declare const VALID_MIME_TYPES: Readonly<Record<string, readonly string[]>>;
|
|
14
|
+
/**
|
|
15
|
+
* Flat set of every extension known to {@link VALID_MIME_TYPES}, used by the
|
|
16
|
+
* extension-only fast path ({@link hasValidExtension}). Derived once at module
|
|
17
|
+
* load — kept in sync with the map automatically.
|
|
18
|
+
*/
|
|
19
|
+
export declare const VALID_FILE_EXTENSIONS: ReadonlySet<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a filename or raw extension to a lowercase no-dot extension token.
|
|
22
|
+
* Returns null if no extension is present.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeExtension(input: string): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Returns true when the given filename ends in a recognized extension.
|
|
27
|
+
* Files with no extension, or extensions that aren't in {@link VALID_FILE_EXTENSIONS},
|
|
28
|
+
* are rejected as "fake" / nonsense.
|
|
29
|
+
*
|
|
30
|
+
* Prefer {@link validateFileType} when a `File` object is available — it catches
|
|
31
|
+
* extension spoofing that this function cannot.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hasValidExtension(filename: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Strict file validation: the browser-reported MIME type must be known, AND the
|
|
36
|
+
* filename's extension must be one of the extensions registered for that type.
|
|
37
|
+
*
|
|
38
|
+
* Rejects:
|
|
39
|
+
* - files with no extension
|
|
40
|
+
* - files whose `type` is empty or unknown to {@link VALID_MIME_TYPES}
|
|
41
|
+
* - files whose extension does not match the registered list for `file.type`
|
|
42
|
+
* (extension-spoofing guard, e.g. `.exe` renamed to `.jpg`)
|
|
43
|
+
*
|
|
44
|
+
* `file.type` comparison is case-insensitive; some browsers lowercase but a few
|
|
45
|
+
* legacy ones don't.
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateFileType(file: File): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Look up every MIME type that legitimately produces the given extension.
|
|
50
|
+
* Useful for `accept`-attribute construction or reverse diagnostics.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getMimeTypesForExtension(extension: string): readonly string[];
|
package/guidelines/Guidelines.md
CHANGED
|
@@ -1,233 +1,233 @@
|
|
|
1
|
-
# FreightWind Design System
|
|
2
|
-
|
|
3
|
-
FreightWind is the Freightos Design System — a React component library styled with Tailwind CSS v4 and FDS (Freightos Design System) tokens.
|
|
4
|
-
|
|
5
|
-
## Complete Setup
|
|
6
|
-
|
|
7
|
-
Follow ALL steps below to properly set up FreightWind in a React project.
|
|
8
|
-
|
|
9
|
-
### Step 1 — Install dependencies
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install @freightos/freightwind @freightos/freightwind/icons tailwindcss @tailwindcss/vite
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
`@freightos/freightwind/icons` is a required peer dependency that provides all iconography. Tailwind CSS v4 is required for component styling.
|
|
16
|
-
|
|
17
|
-
### Step 2 — Configure Tailwind CSS v4
|
|
18
|
-
|
|
19
|
-
FreightWind requires Tailwind CSS v4. Set it up with your bundler:
|
|
20
|
-
|
|
21
|
-
**Vite** — add the Tailwind plugin to `vite.config.ts`:
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
import tailwindcss from '@tailwindcss/vite';
|
|
25
|
-
import { defineConfig } from 'vite';
|
|
26
|
-
import react from '@vitejs/plugin-react';
|
|
27
|
-
|
|
28
|
-
export default defineConfig({
|
|
29
|
-
plugins: [react(), tailwindcss()],
|
|
30
|
-
});
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Step 3 — Set up your global CSS file
|
|
34
|
-
|
|
35
|
-
**CRITICAL**: Your main CSS file must import Tailwind AND FreightWind tokens in the correct order. The tokens.css file uses Tailwind v4's `@theme inline` directive to register color utilities (like `bg-fds-blue-30`, `text-fds-gray-80`, etc.) — this ONLY works when imported inside a CSS file that Tailwind processes.
|
|
36
|
-
|
|
37
|
-
Create or update your main CSS file (e.g. `src/index.css` or `src/styles/globals.css`):
|
|
38
|
-
|
|
39
|
-
```css
|
|
40
|
-
/* Step A: Import Tailwind CSS — this MUST come first */
|
|
41
|
-
@import "tailwindcss";
|
|
42
|
-
|
|
43
|
-
/* Step B: Import FreightWind tokens — MUST be in this same CSS file, AFTER tailwindcss.
|
|
44
|
-
This registers all FDS color utilities (bg-fds-blue-30, text-fds-gray-80, etc.),
|
|
45
|
-
spacing tokens, typography tokens, and custom utilities used by components.
|
|
46
|
-
|
|
47
|
-
DO NOT import this as a <link> tag in HTML — it must be processed by Tailwind's
|
|
48
|
-
CSS engine for @theme inline to work. */
|
|
49
|
-
@import "@freightos/freightwind/tokens.css";
|
|
50
|
-
|
|
51
|
-
/* Step C: Global base styles — REQUIRED for components to render correctly */
|
|
52
|
-
* {
|
|
53
|
-
-webkit-font-smoothing: antialiased;
|
|
54
|
-
-moz-osx-font-smoothing: grayscale;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
body {
|
|
58
|
-
background-color: var(--ground);
|
|
59
|
-
color: var(--foreground);
|
|
60
|
-
font-family: 'Open Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
61
|
-
font-size: 0.875rem; /* 14px — FDS base font size */
|
|
62
|
-
line-height: 1.5;
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**IMPORTANT NOTES:**
|
|
67
|
-
- `@import "@freightos/freightwind/tokens.css"` MUST be inside the same CSS file as `@import "tailwindcss"` — never in a separate file or `<link>` tag
|
|
68
|
-
- The import order matters: `tailwindcss` first, then `tokens.css`
|
|
69
|
-
- If tokens are imported separately or in the wrong order, Tailwind utility classes like `bg-fds-purple-2`, `bg-fds-gray-20`, `text-fds-blue-30`, etc. will NOT work and components will have missing styles
|
|
70
|
-
|
|
71
|
-
### Step 4 — Load the Open Sans font
|
|
72
|
-
|
|
73
|
-
FreightWind uses **Open Sans** as its font family. Load weights 400, 600, and 700 from Google Fonts.
|
|
74
|
-
|
|
75
|
-
**Option A — In your HTML `<head>` (simplest):**
|
|
76
|
-
|
|
77
|
-
```html
|
|
78
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
79
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
80
|
-
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet" />
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Option B — In Next.js with `next/font`:**
|
|
84
|
-
|
|
85
|
-
```tsx
|
|
86
|
-
import { Open_Sans } from 'next/font/google';
|
|
87
|
-
|
|
88
|
-
const openSans = Open_Sans({
|
|
89
|
-
subsets: ['latin'],
|
|
90
|
-
weight: ['400', '600', '700'],
|
|
91
|
-
variable: '--font-open-sans',
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Apply to <html> or <body>:
|
|
95
|
-
<html className={openSans.variable}>
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Then in your CSS, override the font-sans variable:
|
|
99
|
-
|
|
100
|
-
```css
|
|
101
|
-
@theme inline {
|
|
102
|
-
--font-sans: var(--font-open-sans), 'Open Sans', ui-sans-serif, system-ui, sans-serif;
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Step 5 — Import and use components
|
|
107
|
-
|
|
108
|
-
```tsx
|
|
109
|
-
import { Button, Alert, Checkbox } from '@freightos/freightwind';
|
|
110
|
-
import { IconSearch } from '@freightos/freightwind/icons';
|
|
111
|
-
|
|
112
|
-
<Button variant="default" size="md">Submit</Button>
|
|
113
|
-
<Alert variant="info" message="Shipment updated." />
|
|
114
|
-
<Checkbox>Accept terms</Checkbox>
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Step 6 — Toast notifications (optional)
|
|
118
|
-
|
|
119
|
-
If using the `message` toast API, wrap your app with `MessageProvider`:
|
|
120
|
-
|
|
121
|
-
```tsx
|
|
122
|
-
import { MessageProvider, message } from '@freightos/freightwind';
|
|
123
|
-
|
|
124
|
-
// In your root layout:
|
|
125
|
-
<MessageProvider />
|
|
126
|
-
|
|
127
|
-
// Anywhere in your app:
|
|
128
|
-
message.success('Saved successfully');
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Complete minimal example
|
|
132
|
-
|
|
133
|
-
Here is a full working `src/index.css`:
|
|
134
|
-
|
|
135
|
-
```css
|
|
136
|
-
@import "tailwindcss";
|
|
137
|
-
@import "@freightos/freightwind/tokens.css";
|
|
138
|
-
|
|
139
|
-
* {
|
|
140
|
-
-webkit-font-smoothing: antialiased;
|
|
141
|
-
-moz-osx-font-smoothing: grayscale;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
body {
|
|
145
|
-
background-color: var(--ground);
|
|
146
|
-
color: var(--foreground);
|
|
147
|
-
font-family: 'Open Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
148
|
-
font-size: 0.875rem;
|
|
149
|
-
line-height: 1.5;
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
And a full working `src/App.tsx`:
|
|
154
|
-
|
|
155
|
-
```tsx
|
|
156
|
-
import { Button, Alert, Chip, Switch, Checkbox } from '@freightos/freightwind';
|
|
157
|
-
import { IconSearch } from '@freightos/freightwind/icons';
|
|
158
|
-
|
|
159
|
-
function App() {
|
|
160
|
-
return (
|
|
161
|
-
<div className="p-fds-xl">
|
|
162
|
-
<h1 className="text-fds-h3 font-fds-bold leading-fds-title mb-fds-lg">My App</h1>
|
|
163
|
-
<Alert variant="info" message="Welcome to the app!" />
|
|
164
|
-
<div className="mt-fds-lg flex gap-fds-sm">
|
|
165
|
-
<Button variant="default">Primary</Button>
|
|
166
|
-
<Button variant="secondary">Secondary</Button>
|
|
167
|
-
<Button icon="search" tooltip="Search" />
|
|
168
|
-
</div>
|
|
169
|
-
<div className="mt-fds-lg flex gap-fds-md items-center">
|
|
170
|
-
<Chip variant="success">Active</Chip>
|
|
171
|
-
<Switch>Enable notifications</Switch>
|
|
172
|
-
<Checkbox>Accept terms</Checkbox>
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Troubleshooting
|
|
180
|
-
|
|
181
|
-
### Tailwind utility classes not working (e.g. `bg-fds-purple-2` has no effect)
|
|
182
|
-
|
|
183
|
-
This means `tokens.css` is not being processed by Tailwind. Make sure:
|
|
184
|
-
1. `@import "@freightos/freightwind/tokens.css"` is in the SAME CSS file as `@import "tailwindcss"`
|
|
185
|
-
2. It comes AFTER the tailwindcss import
|
|
186
|
-
3. It is NOT loaded via a `<link>` tag — it must be in CSS `@import` so Tailwind processes the `@theme inline` block
|
|
187
|
-
|
|
188
|
-
### Colors like `--ground`, `--foreground`, `--card` are undefined
|
|
189
|
-
|
|
190
|
-
These CSS variables are defined in the `:root` block inside `tokens.css`. Make sure `tokens.css` is imported.
|
|
191
|
-
|
|
192
|
-
### Font looks wrong
|
|
193
|
-
|
|
194
|
-
Make sure Open Sans is loaded (Step 4) and the body `font-family` is set (Step 3C).
|
|
195
|
-
|
|
196
|
-
### Text rendering looks rough or inconsistent
|
|
197
|
-
|
|
198
|
-
Add global font smoothing (Step 3C):
|
|
199
|
-
```css
|
|
200
|
-
* {
|
|
201
|
-
-webkit-font-smoothing: antialiased;
|
|
202
|
-
-moz-osx-font-smoothing: grayscale;
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## Dark mode
|
|
207
|
-
|
|
208
|
-
FreightWind supports dark mode via a `.dark` class on a parent element (typically `<html>`). The tokens.css includes dark mode overrides for all semantic colors. To enable dark mode, add `class="dark"` to the `<html>` element.
|
|
209
|
-
|
|
210
|
-
## Available components
|
|
211
|
-
|
|
212
|
-
See `overview-components.md` for a full list of components with their props and usage.
|
|
213
|
-
|
|
214
|
-
## Available icons
|
|
215
|
-
|
|
216
|
-
See `overview-icons.md` for details on the icon system.
|
|
217
|
-
|
|
218
|
-
## Design tokens
|
|
219
|
-
|
|
220
|
-
FreightWind uses a custom token system instead of Tailwind defaults:
|
|
221
|
-
|
|
222
|
-
- **Colors**: `design-tokens/colors.md` — semantic color palette (blue, red, green, yellow, gray, purple)
|
|
223
|
-
- **Typography**: `design-tokens/typography.md` — font family, sizes, weights, and line heights
|
|
224
|
-
- **Spacing**: `design-tokens/spacing.md` — consistent spacing scale
|
|
225
|
-
|
|
226
|
-
## Key conventions
|
|
227
|
-
|
|
228
|
-
- Always use FDS tokens (`text-fds-*`, `p-fds-*`, `rounded-fds-*`, `bg-fds-*`) instead of Tailwind defaults
|
|
229
|
-
- All components support both controlled and uncontrolled usage patterns
|
|
230
|
-
- Components with `icon` props accept kebab-case icon names: `"search"`, `"close"`, `"user"`, `"risk"`, etc.
|
|
231
|
-
- Use `cn()` utility (exported from the package) for className merging
|
|
232
|
-
- The page canvas background should be `bg-ground` (#f6f6f6), not white
|
|
233
|
-
- Cards and content areas use `bg-card` (#ffffff) against the ground
|
|
1
|
+
# FreightWind Design System
|
|
2
|
+
|
|
3
|
+
FreightWind is the Freightos Design System — a React component library styled with Tailwind CSS v4 and FDS (Freightos Design System) tokens.
|
|
4
|
+
|
|
5
|
+
## Complete Setup
|
|
6
|
+
|
|
7
|
+
Follow ALL steps below to properly set up FreightWind in a React project.
|
|
8
|
+
|
|
9
|
+
### Step 1 — Install dependencies
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @freightos/freightwind @freightos/freightwind/icons tailwindcss @tailwindcss/vite
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`@freightos/freightwind/icons` is a required peer dependency that provides all iconography. Tailwind CSS v4 is required for component styling.
|
|
16
|
+
|
|
17
|
+
### Step 2 — Configure Tailwind CSS v4
|
|
18
|
+
|
|
19
|
+
FreightWind requires Tailwind CSS v4. Set it up with your bundler:
|
|
20
|
+
|
|
21
|
+
**Vite** — add the Tailwind plugin to `vite.config.ts`:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
25
|
+
import { defineConfig } from 'vite';
|
|
26
|
+
import react from '@vitejs/plugin-react';
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
plugins: [react(), tailwindcss()],
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Step 3 — Set up your global CSS file
|
|
34
|
+
|
|
35
|
+
**CRITICAL**: Your main CSS file must import Tailwind AND FreightWind tokens in the correct order. The tokens.css file uses Tailwind v4's `@theme inline` directive to register color utilities (like `bg-fds-blue-30`, `text-fds-gray-80`, etc.) — this ONLY works when imported inside a CSS file that Tailwind processes.
|
|
36
|
+
|
|
37
|
+
Create or update your main CSS file (e.g. `src/index.css` or `src/styles/globals.css`):
|
|
38
|
+
|
|
39
|
+
```css
|
|
40
|
+
/* Step A: Import Tailwind CSS — this MUST come first */
|
|
41
|
+
@import "tailwindcss";
|
|
42
|
+
|
|
43
|
+
/* Step B: Import FreightWind tokens — MUST be in this same CSS file, AFTER tailwindcss.
|
|
44
|
+
This registers all FDS color utilities (bg-fds-blue-30, text-fds-gray-80, etc.),
|
|
45
|
+
spacing tokens, typography tokens, and custom utilities used by components.
|
|
46
|
+
|
|
47
|
+
DO NOT import this as a <link> tag in HTML — it must be processed by Tailwind's
|
|
48
|
+
CSS engine for @theme inline to work. */
|
|
49
|
+
@import "@freightos/freightwind/tokens.css";
|
|
50
|
+
|
|
51
|
+
/* Step C: Global base styles — REQUIRED for components to render correctly */
|
|
52
|
+
* {
|
|
53
|
+
-webkit-font-smoothing: antialiased;
|
|
54
|
+
-moz-osx-font-smoothing: grayscale;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
body {
|
|
58
|
+
background-color: var(--ground);
|
|
59
|
+
color: var(--foreground);
|
|
60
|
+
font-family: 'Open Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
61
|
+
font-size: 0.875rem; /* 14px — FDS base font size */
|
|
62
|
+
line-height: 1.5;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**IMPORTANT NOTES:**
|
|
67
|
+
- `@import "@freightos/freightwind/tokens.css"` MUST be inside the same CSS file as `@import "tailwindcss"` — never in a separate file or `<link>` tag
|
|
68
|
+
- The import order matters: `tailwindcss` first, then `tokens.css`
|
|
69
|
+
- If tokens are imported separately or in the wrong order, Tailwind utility classes like `bg-fds-purple-2`, `bg-fds-gray-20`, `text-fds-blue-30`, etc. will NOT work and components will have missing styles
|
|
70
|
+
|
|
71
|
+
### Step 4 — Load the Open Sans font
|
|
72
|
+
|
|
73
|
+
FreightWind uses **Open Sans** as its font family. Load weights 400, 600, and 700 from Google Fonts.
|
|
74
|
+
|
|
75
|
+
**Option A — In your HTML `<head>` (simplest):**
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
79
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
80
|
+
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet" />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Option B — In Next.js with `next/font`:**
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { Open_Sans } from 'next/font/google';
|
|
87
|
+
|
|
88
|
+
const openSans = Open_Sans({
|
|
89
|
+
subsets: ['latin'],
|
|
90
|
+
weight: ['400', '600', '700'],
|
|
91
|
+
variable: '--font-open-sans',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Apply to <html> or <body>:
|
|
95
|
+
<html className={openSans.variable}>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then in your CSS, override the font-sans variable:
|
|
99
|
+
|
|
100
|
+
```css
|
|
101
|
+
@theme inline {
|
|
102
|
+
--font-sans: var(--font-open-sans), 'Open Sans', ui-sans-serif, system-ui, sans-serif;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Step 5 — Import and use components
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { Button, Alert, Checkbox } from '@freightos/freightwind';
|
|
110
|
+
import { IconSearch } from '@freightos/freightwind/icons';
|
|
111
|
+
|
|
112
|
+
<Button variant="default" size="md">Submit</Button>
|
|
113
|
+
<Alert variant="info" message="Shipment updated." />
|
|
114
|
+
<Checkbox>Accept terms</Checkbox>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Step 6 — Toast notifications (optional)
|
|
118
|
+
|
|
119
|
+
If using the `message` toast API, wrap your app with `MessageProvider`:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { MessageProvider, message } from '@freightos/freightwind';
|
|
123
|
+
|
|
124
|
+
// In your root layout:
|
|
125
|
+
<MessageProvider />
|
|
126
|
+
|
|
127
|
+
// Anywhere in your app:
|
|
128
|
+
message.success('Saved successfully');
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Complete minimal example
|
|
132
|
+
|
|
133
|
+
Here is a full working `src/index.css`:
|
|
134
|
+
|
|
135
|
+
```css
|
|
136
|
+
@import "tailwindcss";
|
|
137
|
+
@import "@freightos/freightwind/tokens.css";
|
|
138
|
+
|
|
139
|
+
* {
|
|
140
|
+
-webkit-font-smoothing: antialiased;
|
|
141
|
+
-moz-osx-font-smoothing: grayscale;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
body {
|
|
145
|
+
background-color: var(--ground);
|
|
146
|
+
color: var(--foreground);
|
|
147
|
+
font-family: 'Open Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
148
|
+
font-size: 0.875rem;
|
|
149
|
+
line-height: 1.5;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
And a full working `src/App.tsx`:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { Button, Alert, Chip, Switch, Checkbox } from '@freightos/freightwind';
|
|
157
|
+
import { IconSearch } from '@freightos/freightwind/icons';
|
|
158
|
+
|
|
159
|
+
function App() {
|
|
160
|
+
return (
|
|
161
|
+
<div className="p-fds-xl">
|
|
162
|
+
<h1 className="text-fds-h3 font-fds-bold leading-fds-title mb-fds-lg">My App</h1>
|
|
163
|
+
<Alert variant="info" message="Welcome to the app!" />
|
|
164
|
+
<div className="mt-fds-lg flex gap-fds-sm">
|
|
165
|
+
<Button variant="default">Primary</Button>
|
|
166
|
+
<Button variant="secondary">Secondary</Button>
|
|
167
|
+
<Button icon="search" tooltip="Search" />
|
|
168
|
+
</div>
|
|
169
|
+
<div className="mt-fds-lg flex gap-fds-md items-center">
|
|
170
|
+
<Chip variant="success">Active</Chip>
|
|
171
|
+
<Switch>Enable notifications</Switch>
|
|
172
|
+
<Checkbox>Accept terms</Checkbox>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Troubleshooting
|
|
180
|
+
|
|
181
|
+
### Tailwind utility classes not working (e.g. `bg-fds-purple-2` has no effect)
|
|
182
|
+
|
|
183
|
+
This means `tokens.css` is not being processed by Tailwind. Make sure:
|
|
184
|
+
1. `@import "@freightos/freightwind/tokens.css"` is in the SAME CSS file as `@import "tailwindcss"`
|
|
185
|
+
2. It comes AFTER the tailwindcss import
|
|
186
|
+
3. It is NOT loaded via a `<link>` tag — it must be in CSS `@import` so Tailwind processes the `@theme inline` block
|
|
187
|
+
|
|
188
|
+
### Colors like `--ground`, `--foreground`, `--card` are undefined
|
|
189
|
+
|
|
190
|
+
These CSS variables are defined in the `:root` block inside `tokens.css`. Make sure `tokens.css` is imported.
|
|
191
|
+
|
|
192
|
+
### Font looks wrong
|
|
193
|
+
|
|
194
|
+
Make sure Open Sans is loaded (Step 4) and the body `font-family` is set (Step 3C).
|
|
195
|
+
|
|
196
|
+
### Text rendering looks rough or inconsistent
|
|
197
|
+
|
|
198
|
+
Add global font smoothing (Step 3C):
|
|
199
|
+
```css
|
|
200
|
+
* {
|
|
201
|
+
-webkit-font-smoothing: antialiased;
|
|
202
|
+
-moz-osx-font-smoothing: grayscale;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Dark mode
|
|
207
|
+
|
|
208
|
+
FreightWind supports dark mode via a `.dark` class on a parent element (typically `<html>`). The tokens.css includes dark mode overrides for all semantic colors. To enable dark mode, add `class="dark"` to the `<html>` element.
|
|
209
|
+
|
|
210
|
+
## Available components
|
|
211
|
+
|
|
212
|
+
See `overview-components.md` for a full list of components with their props and usage.
|
|
213
|
+
|
|
214
|
+
## Available icons
|
|
215
|
+
|
|
216
|
+
See `overview-icons.md` for details on the icon system.
|
|
217
|
+
|
|
218
|
+
## Design tokens
|
|
219
|
+
|
|
220
|
+
FreightWind uses a custom token system instead of Tailwind defaults:
|
|
221
|
+
|
|
222
|
+
- **Colors**: `design-tokens/colors.md` — semantic color palette (blue, red, green, yellow, gray, purple)
|
|
223
|
+
- **Typography**: `design-tokens/typography.md` — font family, sizes, weights, and line heights
|
|
224
|
+
- **Spacing**: `design-tokens/spacing.md` — consistent spacing scale
|
|
225
|
+
|
|
226
|
+
## Key conventions
|
|
227
|
+
|
|
228
|
+
- Always use FDS tokens (`text-fds-*`, `p-fds-*`, `rounded-fds-*`, `bg-fds-*`) instead of Tailwind defaults
|
|
229
|
+
- All components support both controlled and uncontrolled usage patterns
|
|
230
|
+
- Components with `icon` props accept kebab-case icon names: `"search"`, `"close"`, `"user"`, `"risk"`, etc.
|
|
231
|
+
- Use `cn()` utility (exported from the package) for className merging
|
|
232
|
+
- The page canvas background should be `bg-ground` (#f6f6f6), not white
|
|
233
|
+
- Cards and content areas use `bg-card` (#ffffff) against the ground
|