@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 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
- 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, 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 };
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
- 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, 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 };
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.2.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