@camtomlabs/malix-design-system 0.2.0 → 0.3.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 +66 -0
- package/dist/index.cjs +27 -0
- package/dist/index.d.cts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +26 -0
- package/eslint-plugin.cjs +128 -0
- package/package.json +9 -4
- package/src/reset.css +212 -0
- package/src/styles.css +6 -0
package/README.md
CHANGED
|
@@ -23,11 +23,29 @@ npm install react react-dom
|
|
|
23
23
|
Import the bundled stylesheet once in your app's entry point (e.g. `main.tsx`, `layout.tsx`, or `_app.tsx`):
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
|
+
// Optional: opt-in reset scoped to @layer malix-reset.
|
|
27
|
+
// Import BEFORE styles.css so the layer order is established correctly.
|
|
28
|
+
import '@camtomlabs/malix-design-system/reset.css';
|
|
29
|
+
|
|
26
30
|
import '@camtomlabs/malix-design-system/styles.css';
|
|
27
31
|
```
|
|
28
32
|
|
|
29
33
|
That's it. All components are styled and ready to use.
|
|
30
34
|
|
|
35
|
+
### Why the reset is optional
|
|
36
|
+
|
|
37
|
+
`reset.css` lives inside `@layer malix-reset`, which means **any style you
|
|
38
|
+
write outside a layer will automatically win** — no specificity wars, no
|
|
39
|
+
`:not()` hacks. Use it if your project has a hostile global reset that
|
|
40
|
+
makes CSS Modules fight for background colors; skip it if you already
|
|
41
|
+
have your own reset.
|
|
42
|
+
|
|
43
|
+
Layer cascade order:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
malix-reset → malix-tokens → malix-components → app
|
|
47
|
+
```
|
|
48
|
+
|
|
31
49
|
## Usage
|
|
32
50
|
|
|
33
51
|
```tsx
|
|
@@ -43,6 +61,54 @@ export function MyPage() {
|
|
|
43
61
|
}
|
|
44
62
|
```
|
|
45
63
|
|
|
64
|
+
## Icons
|
|
65
|
+
|
|
66
|
+
Malix ships a canonical `<Icon>` wrapper that accepts any icon component
|
|
67
|
+
(lucide-react, phosphor-react, custom SVG) and enforces consistent
|
|
68
|
+
sizing and theming via `currentColor`.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Icon } from '@camtomlabs/malix-design-system';
|
|
72
|
+
import { Plus, Search, Trash } from 'lucide-react';
|
|
73
|
+
|
|
74
|
+
<Icon as={Plus} size="md" label="Add item" /> // 16px, aria-label
|
|
75
|
+
<Icon as={Search} size="sm" /> // decorative (aria-hidden)
|
|
76
|
+
<Icon as={Trash} size="lg" /> // 20px
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Sizes: `'xs'` (12px), `'sm'` (14px), `'md'` (16px), `'lg'` (20px), `'xl'` (24px), or a raw number.
|
|
80
|
+
|
|
81
|
+
## ESLint Plugin — enforce canonical components
|
|
82
|
+
|
|
83
|
+
Malix ships an ESLint plugin that catches raw `<button>` and `<input>`
|
|
84
|
+
elements and tells you to use `<Button>` / `<Input>` from the DS.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
// .eslintrc.cjs
|
|
88
|
+
module.exports = {
|
|
89
|
+
plugins: ['@camtomlabs/malix'],
|
|
90
|
+
rules: {
|
|
91
|
+
'@camtomlabs/malix/no-raw-button': 'warn',
|
|
92
|
+
'@camtomlabs/malix/no-raw-input': 'warn',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or use the recommended preset:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
extends: ['plugin:@camtomlabs/malix/recommended']
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can escape the rule with a standard disable comment when needed:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// eslint-disable-next-line @camtomlabs/malix/no-raw-button
|
|
107
|
+
<button type="submit" form="external-form-id" />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The `no-raw-input` rule allows `type="hidden"` and `type="file"` by default.
|
|
111
|
+
|
|
46
112
|
## Theme Provider
|
|
47
113
|
|
|
48
114
|
Malix includes a React theme provider for managing dark mode:
|
package/dist/index.cjs
CHANGED
|
@@ -53,6 +53,7 @@ __export(index_exports, {
|
|
|
53
53
|
FlyoutMenu: () => FlyoutMenu,
|
|
54
54
|
GlassPopover: () => GlassPopover,
|
|
55
55
|
Header: () => Header,
|
|
56
|
+
Icon: () => Icon,
|
|
56
57
|
Input: () => Input,
|
|
57
58
|
InputGroup: () => InputGroup,
|
|
58
59
|
LanguageSelector: () => LanguageSelector,
|
|
@@ -2650,6 +2651,31 @@ function useMalixTheme() {
|
|
|
2650
2651
|
}
|
|
2651
2652
|
return ctx;
|
|
2652
2653
|
}
|
|
2654
|
+
|
|
2655
|
+
// src/components/Icon.tsx
|
|
2656
|
+
var import_react16 = require("react");
|
|
2657
|
+
var SIZE_MAP = {
|
|
2658
|
+
xs: 12,
|
|
2659
|
+
sm: 14,
|
|
2660
|
+
md: 16,
|
|
2661
|
+
lg: 20,
|
|
2662
|
+
xl: 24
|
|
2663
|
+
};
|
|
2664
|
+
function resolveSize(size) {
|
|
2665
|
+
return typeof size === "number" ? size : SIZE_MAP[size];
|
|
2666
|
+
}
|
|
2667
|
+
var Icon = (0, import_react16.forwardRef)(function Icon2({ as: Component, size = "md", label, ...props }, ref) {
|
|
2668
|
+
const pixelSize = resolveSize(size);
|
|
2669
|
+
const a11y = label ? { role: "img", "aria-label": label } : { "aria-hidden": true, focusable: false };
|
|
2670
|
+
return (0, import_react16.createElement)(Component, {
|
|
2671
|
+
ref,
|
|
2672
|
+
width: pixelSize,
|
|
2673
|
+
height: pixelSize,
|
|
2674
|
+
color: "currentColor",
|
|
2675
|
+
...a11y,
|
|
2676
|
+
...props
|
|
2677
|
+
});
|
|
2678
|
+
});
|
|
2653
2679
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2654
2680
|
0 && (module.exports = {
|
|
2655
2681
|
AIAssistantPanel,
|
|
@@ -2675,6 +2701,7 @@ function useMalixTheme() {
|
|
|
2675
2701
|
FlyoutMenu,
|
|
2676
2702
|
GlassPopover,
|
|
2677
2703
|
Header,
|
|
2704
|
+
Icon,
|
|
2678
2705
|
Input,
|
|
2679
2706
|
InputGroup,
|
|
2680
2707
|
LanguageSelector,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { ReactNode } from 'react';
|
|
3
|
+
import React__default, { ReactNode, SVGProps, ComponentType } from 'react';
|
|
4
4
|
|
|
5
5
|
var version = 1;
|
|
6
6
|
var tokens = [
|
|
@@ -627,4 +627,36 @@ interface MalixThemeProviderProps {
|
|
|
627
627
|
declare function MalixThemeProvider({ children, defaultTheme }: MalixThemeProviderProps): React.FunctionComponentElement<React.ProviderProps<MalixThemeContextValue | null>>;
|
|
628
628
|
declare function useMalixTheme(): MalixThemeContextValue;
|
|
629
629
|
|
|
630
|
-
|
|
630
|
+
type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
|
|
631
|
+
/**
|
|
632
|
+
* Minimal shape that any icon component (lucide-react, phosphor-react,
|
|
633
|
+
* custom SVGs) must satisfy. We accept any React component that takes
|
|
634
|
+
* SVG props so consumers can bring their own icon library and still
|
|
635
|
+
* get consistent sizing and theming.
|
|
636
|
+
*/
|
|
637
|
+
type IconComponent = ComponentType<SVGProps<SVGSVGElement> & {
|
|
638
|
+
size?: number | string;
|
|
639
|
+
}>;
|
|
640
|
+
interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'children' | 'ref'> {
|
|
641
|
+
/** The icon component to render (e.g. any lucide-react icon) */
|
|
642
|
+
as: IconComponent;
|
|
643
|
+
/** Visual size — tokens or raw px. Default: 'md' (16px) */
|
|
644
|
+
size?: IconSize;
|
|
645
|
+
/** Accessible label. If omitted, icon is decorative (aria-hidden) */
|
|
646
|
+
label?: string;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Canonical icon wrapper. Takes any icon component and renders it
|
|
650
|
+
* with consistent sizing aligned to Malix typography tokens and
|
|
651
|
+
* `currentColor` so the icon inherits text color.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* import { Icon } from '@camtomlabs/malix-design-system';
|
|
655
|
+
* import { Plus, Search } from 'lucide-react';
|
|
656
|
+
*
|
|
657
|
+
* <Icon as={Plus} size="md" label="Add item" />
|
|
658
|
+
* <Icon as={Search} size="sm" /> // decorative
|
|
659
|
+
*/
|
|
660
|
+
declare const Icon: React.ForwardRefExoticComponent<IconProps & React.RefAttributes<SVGSVGElement>>;
|
|
661
|
+
|
|
662
|
+
export { type AIAssistantMessage, AIAssistantPanel, type AIAssistantPanelProps, Accordion, type AccordionProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeVariant, Banner, type BannerProps, type BannerVariant, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonHierarchy, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardLevel, type CardProps, ChatBubble, type ChatBubbleProps, type ChatBubbleVariant, ChatInput, type ChatInputProps, Checkbox, type CheckboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CreditsIndicator, type CreditsIndicatorProps, DataTable, type DataTableProps, DateInput, type DateInputProps, Divider, type DividerProps, Dropzone, type DropzoneProps, EmptyState, type EmptyStateProps, FileCard, type FileCardProps, type FilterTabItem, FilterTabs, type FilterTabsProps, FlyoutMenu, type FlyoutMenuItem, type FlyoutMenuProps, GlassPopover, type GlassPopoverProps, Header, type HeaderProps, Icon, type IconComponent, type IconProps, type IconSize, Input, InputGroup, type InputGroupProps, type InputProps, LanguageSelector, type LanguageSelectorOption, type LanguageSelectorProps, type MalixTheme, MalixThemeProvider, Modal, type ModalProps, OnboardingPopover, type OnboardingPopoverProps, OperationStatus, type OperationStatusProps, type OperationStatusType, Overlay, type OverlayProps, Pagination, type PaginationProps, type PaginationVariant, Pill, type PillProps, type PillVariant, PricingCard, type PricingCardProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, Radio, type RadioProps, SearchInput, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, Select, SelectGroup, type SelectGroupProps, type SelectOption, type SelectProps, SelectionCard, type SelectionCardProps, SidebarItem, type SidebarItemProps, SidebarPanel, type SidebarPanelProps, SplitPane, type SplitPaneProps, StatCard, type StatCardChangeType, type StatCardProps, StatusDot, type StatusDotProps, type StatusDotVariant, type StepItem, type StepStatus, Stepper, type StepperProps, TabBar, type TabBarProps, type TabItem, type TableColumn, type TableRow, Textarea, type TextareaProps, Toggle, type ToggleProps, Tooltip, type TooltipPlacement, type TooltipProps, type UserProfileMenuItem, UserProfilePopover, type UserProfilePopoverProps, ValidationAlert, type ValidationAlertProps, type ValidationAlertVariant, tokens_registry as tokenRegistry, useMalixTheme };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { ReactNode } from 'react';
|
|
3
|
+
import React__default, { ReactNode, SVGProps, ComponentType } from 'react';
|
|
4
4
|
|
|
5
5
|
var version = 1;
|
|
6
6
|
var tokens = [
|
|
@@ -627,4 +627,36 @@ interface MalixThemeProviderProps {
|
|
|
627
627
|
declare function MalixThemeProvider({ children, defaultTheme }: MalixThemeProviderProps): React.FunctionComponentElement<React.ProviderProps<MalixThemeContextValue | null>>;
|
|
628
628
|
declare function useMalixTheme(): MalixThemeContextValue;
|
|
629
629
|
|
|
630
|
-
|
|
630
|
+
type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
|
|
631
|
+
/**
|
|
632
|
+
* Minimal shape that any icon component (lucide-react, phosphor-react,
|
|
633
|
+
* custom SVGs) must satisfy. We accept any React component that takes
|
|
634
|
+
* SVG props so consumers can bring their own icon library and still
|
|
635
|
+
* get consistent sizing and theming.
|
|
636
|
+
*/
|
|
637
|
+
type IconComponent = ComponentType<SVGProps<SVGSVGElement> & {
|
|
638
|
+
size?: number | string;
|
|
639
|
+
}>;
|
|
640
|
+
interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'children' | 'ref'> {
|
|
641
|
+
/** The icon component to render (e.g. any lucide-react icon) */
|
|
642
|
+
as: IconComponent;
|
|
643
|
+
/** Visual size — tokens or raw px. Default: 'md' (16px) */
|
|
644
|
+
size?: IconSize;
|
|
645
|
+
/** Accessible label. If omitted, icon is decorative (aria-hidden) */
|
|
646
|
+
label?: string;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Canonical icon wrapper. Takes any icon component and renders it
|
|
650
|
+
* with consistent sizing aligned to Malix typography tokens and
|
|
651
|
+
* `currentColor` so the icon inherits text color.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* import { Icon } from '@camtomlabs/malix-design-system';
|
|
655
|
+
* import { Plus, Search } from 'lucide-react';
|
|
656
|
+
*
|
|
657
|
+
* <Icon as={Plus} size="md" label="Add item" />
|
|
658
|
+
* <Icon as={Search} size="sm" /> // decorative
|
|
659
|
+
*/
|
|
660
|
+
declare const Icon: React.ForwardRefExoticComponent<IconProps & React.RefAttributes<SVGSVGElement>>;
|
|
661
|
+
|
|
662
|
+
export { type AIAssistantMessage, AIAssistantPanel, type AIAssistantPanelProps, Accordion, type AccordionProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeVariant, Banner, type BannerProps, type BannerVariant, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonHierarchy, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardLevel, type CardProps, ChatBubble, type ChatBubbleProps, type ChatBubbleVariant, ChatInput, type ChatInputProps, Checkbox, type CheckboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CreditsIndicator, type CreditsIndicatorProps, DataTable, type DataTableProps, DateInput, type DateInputProps, Divider, type DividerProps, Dropzone, type DropzoneProps, EmptyState, type EmptyStateProps, FileCard, type FileCardProps, type FilterTabItem, FilterTabs, type FilterTabsProps, FlyoutMenu, type FlyoutMenuItem, type FlyoutMenuProps, GlassPopover, type GlassPopoverProps, Header, type HeaderProps, Icon, type IconComponent, type IconProps, type IconSize, Input, InputGroup, type InputGroupProps, type InputProps, LanguageSelector, type LanguageSelectorOption, type LanguageSelectorProps, type MalixTheme, MalixThemeProvider, Modal, type ModalProps, OnboardingPopover, type OnboardingPopoverProps, OperationStatus, type OperationStatusProps, type OperationStatusType, Overlay, type OverlayProps, Pagination, type PaginationProps, type PaginationVariant, Pill, type PillProps, type PillVariant, PricingCard, type PricingCardProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, Radio, type RadioProps, SearchInput, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, Select, SelectGroup, type SelectGroupProps, type SelectOption, type SelectProps, SelectionCard, type SelectionCardProps, SidebarItem, type SidebarItemProps, SidebarPanel, type SidebarPanelProps, SplitPane, type SplitPaneProps, StatCard, type StatCardChangeType, type StatCardProps, StatusDot, type StatusDotProps, type StatusDotVariant, type StepItem, type StepStatus, Stepper, type StepperProps, TabBar, type TabBarProps, type TabItem, type TableColumn, type TableRow, Textarea, type TextareaProps, Toggle, type ToggleProps, Tooltip, type TooltipPlacement, type TooltipProps, type UserProfileMenuItem, UserProfilePopover, type UserProfilePopoverProps, ValidationAlert, type ValidationAlertProps, type ValidationAlertVariant, tokens_registry as tokenRegistry, useMalixTheme };
|
package/dist/index.js
CHANGED
|
@@ -2559,6 +2559,31 @@ function useMalixTheme() {
|
|
|
2559
2559
|
}
|
|
2560
2560
|
return ctx;
|
|
2561
2561
|
}
|
|
2562
|
+
|
|
2563
|
+
// src/components/Icon.tsx
|
|
2564
|
+
import { createElement as createElement2, forwardRef } from "react";
|
|
2565
|
+
var SIZE_MAP = {
|
|
2566
|
+
xs: 12,
|
|
2567
|
+
sm: 14,
|
|
2568
|
+
md: 16,
|
|
2569
|
+
lg: 20,
|
|
2570
|
+
xl: 24
|
|
2571
|
+
};
|
|
2572
|
+
function resolveSize(size) {
|
|
2573
|
+
return typeof size === "number" ? size : SIZE_MAP[size];
|
|
2574
|
+
}
|
|
2575
|
+
var Icon = forwardRef(function Icon2({ as: Component, size = "md", label, ...props }, ref) {
|
|
2576
|
+
const pixelSize = resolveSize(size);
|
|
2577
|
+
const a11y = label ? { role: "img", "aria-label": label } : { "aria-hidden": true, focusable: false };
|
|
2578
|
+
return createElement2(Component, {
|
|
2579
|
+
ref,
|
|
2580
|
+
width: pixelSize,
|
|
2581
|
+
height: pixelSize,
|
|
2582
|
+
color: "currentColor",
|
|
2583
|
+
...a11y,
|
|
2584
|
+
...props
|
|
2585
|
+
});
|
|
2586
|
+
});
|
|
2562
2587
|
export {
|
|
2563
2588
|
AIAssistantPanel,
|
|
2564
2589
|
Accordion,
|
|
@@ -2583,6 +2608,7 @@ export {
|
|
|
2583
2608
|
FlyoutMenu,
|
|
2584
2609
|
GlassPopover,
|
|
2585
2610
|
Header,
|
|
2611
|
+
Icon,
|
|
2586
2612
|
Input,
|
|
2587
2613
|
InputGroup,
|
|
2588
2614
|
LanguageSelector,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @camtomlabs/malix-design-system/eslint-plugin
|
|
3
|
+
*
|
|
4
|
+
* Custom ESLint rules that enforce usage of canonical Malix components
|
|
5
|
+
* instead of raw HTML elements.
|
|
6
|
+
*
|
|
7
|
+
* Usage in consuming repo:
|
|
8
|
+
*
|
|
9
|
+
* // .eslintrc.cjs
|
|
10
|
+
* module.exports = {
|
|
11
|
+
* plugins: ['@camtomlabs/malix'],
|
|
12
|
+
* rules: {
|
|
13
|
+
* '@camtomlabs/malix/no-raw-button': 'warn',
|
|
14
|
+
* '@camtomlabs/malix/no-raw-input': 'warn',
|
|
15
|
+
* },
|
|
16
|
+
* };
|
|
17
|
+
*
|
|
18
|
+
* Both rules allow an escape hatch via the disable comment:
|
|
19
|
+
* // eslint-disable-next-line @camtomlabs/malix/no-raw-button
|
|
20
|
+
* <button type="submit" form="external-form-id" />
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
26
|
+
const noRawButton = {
|
|
27
|
+
meta: {
|
|
28
|
+
type: 'suggestion',
|
|
29
|
+
docs: {
|
|
30
|
+
description:
|
|
31
|
+
'Disallow raw <button> elements. Use <Button> from @camtomlabs/malix-design-system instead.',
|
|
32
|
+
},
|
|
33
|
+
schema: [
|
|
34
|
+
{
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
allow: {
|
|
38
|
+
type: 'array',
|
|
39
|
+
items: { type: 'string' },
|
|
40
|
+
description: 'JSX element names that are allowed (e.g. ["Button", "MenuButton"])',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
messages: {
|
|
47
|
+
rawButton:
|
|
48
|
+
'Do not use raw <button>. Import Button from "@camtomlabs/malix-design-system" and use <Button hierarchy="primary">.',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
create(context) {
|
|
52
|
+
return {
|
|
53
|
+
JSXOpeningElement(node) {
|
|
54
|
+
if (node.name.type !== 'JSXIdentifier') return;
|
|
55
|
+
if (node.name.name !== 'button') return;
|
|
56
|
+
context.report({ node, messageId: 'rawButton' });
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
63
|
+
const noRawInput = {
|
|
64
|
+
meta: {
|
|
65
|
+
type: 'suggestion',
|
|
66
|
+
docs: {
|
|
67
|
+
description:
|
|
68
|
+
'Disallow raw <input> elements (except type="hidden"). Use <Input> from @camtomlabs/malix-design-system instead.',
|
|
69
|
+
},
|
|
70
|
+
schema: [
|
|
71
|
+
{
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
allowTypes: {
|
|
75
|
+
type: 'array',
|
|
76
|
+
items: { type: 'string' },
|
|
77
|
+
default: ['hidden', 'file'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
messages: {
|
|
84
|
+
rawInput:
|
|
85
|
+
'Do not use raw <input>. Import Input from "@camtomlabs/malix-design-system" and use <Input>.',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
create(context) {
|
|
89
|
+
const options = context.options[0] || {};
|
|
90
|
+
const allowTypes = options.allowTypes || ['hidden', 'file'];
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
JSXOpeningElement(node) {
|
|
94
|
+
if (node.name.type !== 'JSXIdentifier') return;
|
|
95
|
+
if (node.name.name !== 'input') return;
|
|
96
|
+
|
|
97
|
+
// Allow inputs whose type attribute is in allowTypes (e.g. hidden, file)
|
|
98
|
+
const typeAttr = node.attributes.find(
|
|
99
|
+
(attr) =>
|
|
100
|
+
attr.type === 'JSXAttribute' &&
|
|
101
|
+
attr.name &&
|
|
102
|
+
attr.name.name === 'type' &&
|
|
103
|
+
attr.value &&
|
|
104
|
+
attr.value.type === 'Literal',
|
|
105
|
+
);
|
|
106
|
+
if (typeAttr && allowTypes.includes(typeAttr.value.value)) return;
|
|
107
|
+
|
|
108
|
+
context.report({ node, messageId: 'rawInput' });
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
rules: {
|
|
116
|
+
'no-raw-button': noRawButton,
|
|
117
|
+
'no-raw-input': noRawInput,
|
|
118
|
+
},
|
|
119
|
+
configs: {
|
|
120
|
+
recommended: {
|
|
121
|
+
plugins: ['@camtomlabs/malix'],
|
|
122
|
+
rules: {
|
|
123
|
+
'@camtomlabs/malix/no-raw-button': 'warn',
|
|
124
|
+
'@camtomlabs/malix/no-raw-input': 'warn',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camtomlabs/malix-design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Malix Design System combined package with components, tokens, and bundled styles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -8,15 +8,18 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"sideEffects": [
|
|
10
10
|
"./src/styles.css",
|
|
11
|
-
"./src/tokens.css"
|
|
11
|
+
"./src/tokens.css",
|
|
12
|
+
"./src/reset.css"
|
|
12
13
|
],
|
|
13
14
|
"files": [
|
|
14
15
|
"dist",
|
|
15
16
|
"src/styles.css",
|
|
16
17
|
"src/tokens.css",
|
|
18
|
+
"src/reset.css",
|
|
17
19
|
"src/tokens.registry.json",
|
|
18
20
|
"tailwind.preset.js",
|
|
19
|
-
"stylelint.config.cjs"
|
|
21
|
+
"stylelint.config.cjs",
|
|
22
|
+
"eslint-plugin.cjs"
|
|
20
23
|
],
|
|
21
24
|
"repository": {
|
|
22
25
|
"type": "git",
|
|
@@ -44,9 +47,11 @@
|
|
|
44
47
|
"./css": "./src/styles.css",
|
|
45
48
|
"./styles.css": "./src/styles.css",
|
|
46
49
|
"./tokens.css": "./src/tokens.css",
|
|
50
|
+
"./reset.css": "./src/reset.css",
|
|
47
51
|
"./tokens.registry.json": "./src/tokens.registry.json",
|
|
48
52
|
"./tailwind.preset": "./tailwind.preset.js",
|
|
49
|
-
"./stylelint.config": "./stylelint.config.cjs"
|
|
53
|
+
"./stylelint.config": "./stylelint.config.cjs",
|
|
54
|
+
"./eslint-plugin": "./eslint-plugin.cjs"
|
|
50
55
|
},
|
|
51
56
|
"peerDependencies": {
|
|
52
57
|
"react": "^18.0.0 || ^19.0.0",
|
package/src/reset.css
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════
|
|
2
|
+
MALIX RESET — Opt-in CSS reset scoped to @layer
|
|
3
|
+
|
|
4
|
+
Ships inside the `malix-reset` layer so any style
|
|
5
|
+
defined outside this layer (or in a later layer)
|
|
6
|
+
will win regardless of selector specificity.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
import '@camtomlabs/malix-design-system/reset.css';
|
|
10
|
+
|
|
11
|
+
This file defines the cascade order. Import it
|
|
12
|
+
ONCE, before any other Malix stylesheet:
|
|
13
|
+
|
|
14
|
+
1. malix-reset (this file)
|
|
15
|
+
2. malix-tokens (CSS custom properties)
|
|
16
|
+
3. malix-components (component styles)
|
|
17
|
+
4. app (your own styles)
|
|
18
|
+
|
|
19
|
+
Any CSS you write outside these layers will
|
|
20
|
+
automatically beat the reset — no :not() hacks,
|
|
21
|
+
no specificity wars.
|
|
22
|
+
═══════════════════════════════════════════════ */
|
|
23
|
+
|
|
24
|
+
@layer malix-reset, malix-tokens, malix-components, app;
|
|
25
|
+
|
|
26
|
+
@layer malix-reset {
|
|
27
|
+
*,
|
|
28
|
+
*::before,
|
|
29
|
+
*::after {
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
html {
|
|
34
|
+
-webkit-text-size-adjust: 100%;
|
|
35
|
+
-moz-tab-size: 4;
|
|
36
|
+
tab-size: 4;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
body {
|
|
40
|
+
margin: 0;
|
|
41
|
+
line-height: 1.5;
|
|
42
|
+
-webkit-font-smoothing: antialiased;
|
|
43
|
+
-moz-osx-font-smoothing: grayscale;
|
|
44
|
+
font-family: var(--malix-font-body, system-ui, sans-serif);
|
|
45
|
+
color: var(--malix-foreground, #111827);
|
|
46
|
+
background-color: var(--malix-background-main, #ffffff);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
hr {
|
|
50
|
+
height: 0;
|
|
51
|
+
color: inherit;
|
|
52
|
+
border-top-width: 1px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
h1, h2, h3, h4, h5, h6 {
|
|
56
|
+
font-size: inherit;
|
|
57
|
+
font-weight: inherit;
|
|
58
|
+
margin: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
p, blockquote, dl, dd {
|
|
62
|
+
margin: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
ul, ol {
|
|
66
|
+
margin: 0;
|
|
67
|
+
padding: 0;
|
|
68
|
+
list-style: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
a {
|
|
72
|
+
color: inherit;
|
|
73
|
+
text-decoration: inherit;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
b, strong {
|
|
77
|
+
font-weight: bolder;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
code, kbd, samp, pre {
|
|
81
|
+
font-family:
|
|
82
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
|
83
|
+
"Liberation Mono", "Courier New", monospace;
|
|
84
|
+
font-size: 1em;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
small {
|
|
88
|
+
font-size: 80%;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
sub, sup {
|
|
92
|
+
font-size: 75%;
|
|
93
|
+
line-height: 0;
|
|
94
|
+
position: relative;
|
|
95
|
+
vertical-align: baseline;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
sub { bottom: -0.25em; }
|
|
99
|
+
sup { top: -0.5em; }
|
|
100
|
+
|
|
101
|
+
table {
|
|
102
|
+
border-collapse: collapse;
|
|
103
|
+
border-color: inherit;
|
|
104
|
+
text-indent: 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
button,
|
|
108
|
+
input,
|
|
109
|
+
optgroup,
|
|
110
|
+
select,
|
|
111
|
+
textarea {
|
|
112
|
+
font-family: inherit;
|
|
113
|
+
font-size: 100%;
|
|
114
|
+
font-weight: inherit;
|
|
115
|
+
line-height: inherit;
|
|
116
|
+
color: inherit;
|
|
117
|
+
margin: 0;
|
|
118
|
+
padding: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
button,
|
|
122
|
+
select {
|
|
123
|
+
text-transform: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* IMPORTANT: intentionally NOT resetting button background-color.
|
|
127
|
+
Components or CSS Modules must be able to set their own background
|
|
128
|
+
without fighting a global reset. */
|
|
129
|
+
button,
|
|
130
|
+
[type='button'],
|
|
131
|
+
[type='reset'],
|
|
132
|
+
[type='submit'] {
|
|
133
|
+
-webkit-appearance: button;
|
|
134
|
+
background-color: transparent;
|
|
135
|
+
background-image: none;
|
|
136
|
+
border: 0;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
:-moz-focusring {
|
|
141
|
+
outline: auto;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
:-moz-ui-invalid {
|
|
145
|
+
box-shadow: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
progress {
|
|
149
|
+
vertical-align: baseline;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
::-webkit-inner-spin-button,
|
|
153
|
+
::-webkit-outer-spin-button {
|
|
154
|
+
height: auto;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
[type='search'] {
|
|
158
|
+
-webkit-appearance: textfield;
|
|
159
|
+
outline-offset: -2px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
::-webkit-search-decoration {
|
|
163
|
+
-webkit-appearance: none;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
::-webkit-file-upload-button {
|
|
167
|
+
-webkit-appearance: button;
|
|
168
|
+
font: inherit;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
summary {
|
|
172
|
+
display: list-item;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fieldset {
|
|
176
|
+
margin: 0;
|
|
177
|
+
padding: 0;
|
|
178
|
+
border: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
legend {
|
|
182
|
+
padding: 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
textarea {
|
|
186
|
+
resize: vertical;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
input::placeholder,
|
|
190
|
+
textarea::placeholder {
|
|
191
|
+
opacity: 1;
|
|
192
|
+
color: var(--malix-placeholder, #9ca3af);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
[role="button"] {
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
:disabled {
|
|
200
|
+
cursor: default;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
img, svg, video, canvas, audio, iframe, embed, object {
|
|
204
|
+
display: block;
|
|
205
|
+
vertical-align: middle;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
img, video {
|
|
209
|
+
max-width: 100%;
|
|
210
|
+
height: auto;
|
|
211
|
+
}
|
|
212
|
+
}
|
package/src/styles.css
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
@import "./tokens.css";
|
|
2
2
|
|
|
3
|
+
/* Declare the layer here (harmless if reset.css isn't imported) so
|
|
4
|
+
consumers that skip reset.css still get a named layer for our
|
|
5
|
+
component styles. When reset.css IS imported, this declaration
|
|
6
|
+
merges with the one there and the cascade order is preserved. */
|
|
7
|
+
@layer malix-reset, malix-tokens, malix-components, app;
|
|
8
|
+
|
|
3
9
|
/* ═══════════════════════════════════════════════
|
|
4
10
|
BUTTON — Pencil: KCjkg, 5eAkI, zMyI0, zUoM5
|
|
5
11
|
padding: 10px 20px, gap: 8px, radius-md
|