@sableui/react 0.6.0 → 0.6.1
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/CHANGELOG.md +13 -0
- package/bin/sable-lint.mjs +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/registry.json +5 -1
- package/scripts/grade.mjs +2 -2
- package/scripts/label-contrast.mjs +36 -0
- package/scripts/lint-rules.mjs +21 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# What's new
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
- **`sable lint` catches a status fill retuned without its text.** Since 0.6,
|
|
6
|
+
`text-destructive`, `border-destructive`, `ring-destructive` and the rest read
|
|
7
|
+
`--destructive-text`; only `bg-destructive` reads the fill. An app that set
|
|
8
|
+
`--destructive` (or `--success`, `--tone-warning`, `--tone-info`) and stopped
|
|
9
|
+
there painted its buttons in its own red and its error text, borders and rings
|
|
10
|
+
in the theme's, and nothing said so. The new consumer rule `status-text-drift`
|
|
11
|
+
reads the app's stylesheets for a status fill set without its `-text` in the
|
|
12
|
+
same theme and mode, and names the mode that is missing. It needs only the
|
|
13
|
+
stylesheet, so it reports even where `label-contrast` cannot measure. `sable
|
|
14
|
+
grade` runs it too.
|
|
15
|
+
|
|
3
16
|
## 0.6.0
|
|
4
17
|
|
|
5
18
|
### Art from the theme
|
package/bin/sable-lint.mjs
CHANGED
|
@@ -27,7 +27,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
|
|
|
27
27
|
import { dirname, join, relative, resolve, sep } from "node:path"
|
|
28
28
|
import { fileURLToPath } from "node:url"
|
|
29
29
|
import { ignoreGlob, lintText, makeRules, report } from "../scripts/lint-rules.mjs"
|
|
30
|
-
import { labelContrastScanner } from "../scripts/label-contrast.mjs"
|
|
30
|
+
import { labelContrastScanner, statusTextDrift } from "../scripts/label-contrast.mjs"
|
|
31
31
|
|
|
32
32
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
33
33
|
const PKG_ROOT = join(HERE, "..")
|
|
@@ -54,7 +54,7 @@ const motionTokens = new Set([
|
|
|
54
54
|
REGISTRY.tokens?.motion?.scale,
|
|
55
55
|
].filter(Boolean))
|
|
56
56
|
|
|
57
|
-
const rules = makeRules({ semanticTokens, motionTokens, labelContrast: await labelContrastScanner(PKG_ROOT, REGISTRY.themes?.default) }).filter((r) => r.scope !== "package")
|
|
57
|
+
const rules = makeRules({ semanticTokens, motionTokens, labelContrast: await labelContrastScanner(PKG_ROOT, REGISTRY.themes?.default), statusText: (css) => statusTextDrift(css, REGISTRY.themes?.default) }).filter((r) => r.scope !== "package")
|
|
58
58
|
|
|
59
59
|
/* ── Args. ─────────────────────────────────────────────────────────────────── */
|
|
60
60
|
const args = process.argv.slice(2)
|
package/dist/index.d.ts
CHANGED
|
@@ -7640,6 +7640,6 @@ interface UseCopyResult {
|
|
|
7640
7640
|
*/
|
|
7641
7641
|
declare function useCopy(text: string, { timeout }?: UseCopyOptions): UseCopyResult;
|
|
7642
7642
|
|
|
7643
|
-
declare const SABLE_UI_VERSION: "0.6.
|
|
7643
|
+
declare const SABLE_UI_VERSION: "0.6.1";
|
|
7644
7644
|
|
|
7645
7645
|
export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, AppHeader, type AppHeaderProps, AppShell, type AppShellProps, AreaChart, type AreaChartProps, type ArtTone, Attachment, type AttachmentProps, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, type AvatarGroupCountProps, type AvatarGroupProps, AvatarImage, type AvatarProps, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, type BadgeAppearance, type BadgeProps, type BadgeTone, Banner, type BannerProps, BarChart, type BarChartProps, BottomNav, BottomNavItem, type BottomNavItemProps, BreadcrumbItem, type BreadcrumbItemProps, BreadcrumbLink, type BreadcrumbLinkProps, BreadcrumbPage, type BreadcrumbPageProps, BreadcrumbSeparator, type BreadcrumbSeparatorProps, Breadcrumbs, type BreadcrumbsProps, Browser, type BrowserProps, Bubble, type BubbleProps, BubbleReaction, type BubbleReactionProps, BubbleReactions, type BubbleReactionsProps, Button, type ButtonProps, CHART_SERIES_COUNT, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, type CalendarProps, Card, CardAction, CardContent, type CardContentProps, CardDescription, CardFooter, type CardFooterProps, CardHeader, CardList, type CardProps, CardTitle, type CardTitleProps, Carousel, type CarouselProps, type CarouselSlidesToShow, type ChartCategories, type ChartCenterLabel, type ChartCurve, type ChartDatum, type ChartSeries, type ChartTone, type ChartTooltipIndicator, type ChartTooltipOptions, type ChartValueFormatter, ChatComposer, type ChatComposerProps, Checkbox, CheckboxCard, type CheckboxCardProps, type CheckboxCardTone, CheckboxGroup, type CheckboxGroupProps, Code, CodeBlock, type CodeBlockProps, CodeHighlightProvider, type CodeHighlighter, type CodeProps, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, type ColorFieldProps, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, type ComboboxItemGroup, ComboboxItemIndicator, ComboboxList, ComboboxPortal, type ComboboxProps, ComboboxValue, ComboboxVirtualList, type ComboboxVirtualListProps, type CommandFilter, type CommandItem, CommandMenu, CommandMenuGroup, type CommandMenuGroupProps, CommandMenuItem, type CommandMenuItemProps, CommandMenuPanel, type CommandMenuPanelProps, type CommandMenuProps, CommandMenuTrigger, type CommandMenuTriggerProps, type ComponentGroup, type ComponentGuidance, ComponentMeta, type ComponentRegister, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, DateTile, type DateTileProps, Density, DensityProvider, Density as DensityValue, DestructiveActionModal, type DestructiveActionModalProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, type DialogSize, DialogTitle, DialogTrigger, EmptyState, type EmptyStateProps, Entity, type EntityProps, Feedback, type FeedbackData, type FeedbackProps, type FeedbackRating, Field, type FieldProps, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, type FileDropzoneProps, type FileRejection, type FileRejectionCode, Form, type FormProps, GlidingHighlight, type GlidingHighlightProps, GradientArt, type GradientArtProps, type Hsv, InlineStatus, type InlineStatusProps, type StatusTone as InlineTone, Input, type InputProps$1 as InputProps, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, Label, type LabelProps$2 as LabelProps, LineChart, type LineChartProps, Marker, type MarkerProps, Marquee, type MarqueeProps, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, type MessageProps, MessageScroller, type MessageScrollerHandle, type MessageScrollerProps, Meter, MeterIndicator, MeterLabel, type MeterSize, type MeterTone, MeterTrack, MeterValue, MiddleTruncate, type MiddleTruncateProps, type MotionPolicy, MotionProvider, type MotionProviderProps, Note, type NoteAction, type NoteProps, NotificationBadge, type NotificationBadgeProps, NumberField, type NumberFieldProps, type OrbMode, type OrbSize, OtpField, type OtpFieldProps, OverflowMenu, type OverflowMenuProps, PageHeader, type PageHeaderProps, PageLoader, type PageLoaderProps, Pagination, type PaginationProps, Panel, PanelGroup, PanelResizer, type PanelResizerProps, PanelSection, type PanelWidthBounds, Phone, type PhoneProps, PieChart, type PieChartProps, Pill, type PillProps, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, Presence, type PresenceProps, type Presentation, PresentationProvider, Progress, ProgressIndicator, ProgressLabel, type ProgressSize, ProgressTrack, ProgressValue, PropertyPair, PropertyRow, RadarChart, type RadarChartProps, RadialChart, type RadialChartProps, Radio, RadioCard, type RadioCardProps, type RadioCardTone, RadioGroup, RelativeTime, type RelativeTimeProps, Reveal, type RevealProps, type Rgb, SABLE_UI_VERSION, SEGMENTED_BAR_TONES, STATUS_TONES, STATUS_TONE_ICON, type ScrollCommandOptions, Scroller, type ScrollerProps, ScrubField, type ScrubFieldProps, SearchInput, type SearchInputProps, SegmentedBar, type SegmentedBarProps, type SegmentedBarSegment, type SegmentedBarTone, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps$3 as SeparatorProps, SettingsMenu, type SettingsMenuProps, SettingsRow, type SettingsRowProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, type ShowMoreProps, SideNav, type SideNavActiveVariant, SideNavGroup, type SideNavGroupProps, SideNavItem, type SideNavItemProps, type SideNavProps, type SideNavSize, Sidebar, SidebarFooter, SidebarHeader, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarProps, SimpleTooltip, Skeleton, Slider, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue, Sparkline, type SparklineProps, Spinner, type SpinnerProps, StatusBar, StatusBarGroup, StatusBarSeparator, StatusDot, type StatusDotProps, type StatusTone, type StepStatus, Stepper, StepperItem, type StepperItemProps, type StepperOrientation, type StepperProps, StreamingText, type StreamingTextProps, Switch, TRANSPARENT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsPanel as TabsContent, TabsList, TabsPanel, TabsTrigger, TextReveal, type TextRevealProps, Textarea, type TextareaProps, ThemeId, ThemeManifest, ThemeMode$1 as ThemeManifestMode, type ThemeMode, ThemeSelector, type ThemeSelectorProps, ThemeSwitcher, type ThemeSwitcherProps, ThinkingOrb, type ThinkingOrbProps, ToastAction, type ToastApi, ToastClose, ToastContent, ToastDescription, type ToastManagerApi, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, ToastRoot, ToastTitle, type ToastType, ToastViewport, Toggle, ToggleGroup, type ToggleGroupAppearance, Toggle as ToggleGroupItem, Toolbar, ToolbarButton, type ToolbarButtonProps, ToolbarGroup, type ToolbarGroupProps, ToolbarInput, type ToolbarInputProps, ToolbarLink, type ToolbarLinkProps, type ToolbarProps, ToolbarSeparator, type ToolbarSeparatorProps, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, type UseCopyOptions, type UseCopyResult, type UseInViewOptions, type UsePanelWidthOptions, Video, type VideoProps, ViewTransitionName, type ViewTransitionNameProps, Workspace, WorkspaceBar, WorkspaceCanvas, accordionMeta, appHeaderMeta, appShellMeta, areaChartMeta, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bannerMeta, bannerVariants, barChartMeta, bottomNavMeta, breadcrumbsMeta, browserMeta, bubbleMeta, buttonMeta, buttonVariants, calendarMeta, cardMeta, carouselMeta, chartColor, toneColor as chartToneColor, chatComposerMeta, checkboxCardMeta, checkboxMeta, cn, codeBlockMeta, codeMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, commandMenuMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, dataTableMeta, datePickerMeta, dateTileMeta, destructiveActionModalMeta, dialogMeta, emptyStateMeta, entityMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, formMeta, formatFileSize, formatHex, glidingHighlightMeta, gradientArtMeta, hsvToRgb, hueColor, inlineStatusMeta, inputMeta, isTransparent, kbdMeta, labelMeta, lineChartMeta, markerMeta, marqueeMeta, menuMeta, messageMeta, messageScrollerMeta, meterIndicatorVariants, meterMeta, meterTrackVariants, middleTruncateMeta, noteMeta, noteVariants, notificationBadgeMeta, notificationCountVariants, notificationDotVariants, numberFieldMeta, otpFieldMeta, overflowMenuMeta, pageHeaderMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, phoneMeta, pieChartMeta, pillMeta, pillVariants, popoverMeta, progressMeta, progressTrackVariants, radarChartMeta, radialChartMeta, radioCardMeta, radioMeta, relativeTimeMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, searchInputMeta, segmentedBarMeta, segmentedBarSegmentVariants, segmentedBarVariants, selectMeta, separatorMeta, settingsMenuMeta, sheetMeta, sheetVariants, showMoreMeta, sideNavMeta, sidebarMeta, skeletonMeta, sliderMeta, sparklineMeta, spinnerMeta, startViewTransition, statusBarMeta, statusDotMeta, statusDotVariants, stepperConnectorVariants, stepperIndicatorVariants, stepperMeta, streamingTextMeta, supportsViewTransitions, switchMeta, tableMeta, tabsMeta, textareaMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, toggleMeta, toolbarGroupVariants, toolbarMeta, toolbarSeparatorVariants, toolbarVariants, tooltipMeta, treeIndent, treeMeta, useAnchoredDensity, useComboboxFilteredItems, useCommandMenuShortcut, useCopy, useDensity, useInView, useMotion, usePanelWidth, usePresentation, useReducedMotion, useScrollActivity, useSidebar, useToast, useToastManager, useTypeTier, videoMeta, workspaceMeta };
|
package/dist/index.js
CHANGED
|
@@ -11347,6 +11347,6 @@ var carouselMeta = {
|
|
|
11347
11347
|
};
|
|
11348
11348
|
|
|
11349
11349
|
// src/index.ts
|
|
11350
|
-
var SABLE_UI_VERSION = "0.6.
|
|
11350
|
+
var SABLE_UI_VERSION = "0.6.1";
|
|
11351
11351
|
|
|
11352
11352
|
export { Banner, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Breadcrumbs, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, Carousel, CheckboxCard, CheckboxGroup, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxList, ComboboxPortal, ComboboxValue, ComboboxVirtualList, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DatePicker, Feedback, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, GlidingHighlight, InlineStatus, LineChart, Marquee, MiddleTruncate, Note, NumberField, PageLoader, Pagination, Panel, PanelGroup, PanelResizer, PanelSection, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, PropertyPair, PropertyRow, RadarChart, Radio, RadioCard, RadioGroup, SABLE_UI_VERSION, STATUS_TONES, STATUS_TONE_ICON, ScrubField, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, StatusBar, StatusBarGroup, StatusBarSeparator, TRANSPARENT, ThemeSelector, ThemeSwitcher, ThinkingOrb, ToastAction, ToastClose, ToastContent, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, Video, Workspace, WorkspaceBar, WorkspaceCanvas, bannerMeta, bannerVariants, breadcrumbsMeta, calendarMeta, carouselMeta, checkboxCardMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, datePickerMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, formatFileSize, formatHex, glidingHighlightMeta, hsvToRgb, hueColor, inlineStatusMeta, isTransparent, lineChartMeta, marqueeMeta, middleTruncateMeta, noteMeta, noteVariants, numberFieldMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, pillMeta, popoverMeta, radarChartMeta, radioCardMeta, radioMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, sheetMeta, sheetVariants, showMoreMeta, statusBarMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, treeIndent, treeMeta, useComboboxFilteredItems, usePanelWidth, useToast, useToastManager, videoMeta, workspaceMeta };
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://sable.design/registry.schema.json",
|
|
3
3
|
"name": "@sableui/react",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Sable.",
|
|
6
6
|
"engine": {
|
|
7
7
|
"behavior": "@base-ui/react",
|
|
@@ -1564,6 +1564,10 @@
|
|
|
1564
1564
|
{
|
|
1565
1565
|
"id": "label-contrast",
|
|
1566
1566
|
"fails-on": "A label on a brand fill (`--primary`, `--sidebar-primary`, `--highlight`, each with its `-foreground`) should clear 4.5:1 AND APCA Lc 60 — a pair can pass the ratio and still read muddy (dark ink on a bright red-orange, Lc 42). The finding names the nearest pair that reads: the ink the colour takes as picked, with the fill's lightness moved to meet it, hue kept. If the pair is your brand's deliberate choice, keep it on the record: `/* design-lint-disable-next-line label-contrast -- <reason> */` above the declaration."
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
"id": "status-text-drift",
|
|
1570
|
+
"fails-on": "A status colour has two tokens: the fill (`--destructive`, `--success`, `--tone-warning`, `--tone-info`), which `bg-*` paints, and its `-text` (`--destructive-text` …), which `text-*`, `border-*`, `ring-*`, `outline-*`, `fill-*` and `stroke-*` read. Setting the fill alone leaves the text on the theme's colour, so a retuned red button sits beside the theme's red error message. Set the `-text` in the same theme and mode: the fill itself where it already reads on your surfaces, or a step of it moved away from them (lighter in dark mode) so it clears 4.5:1 on a card."
|
|
1567
1571
|
}
|
|
1568
1572
|
],
|
|
1569
1573
|
"escapeHatch": {
|
package/scripts/grade.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import { createHash } from "node:crypto"
|
|
|
34
34
|
import { dirname, extname, join, relative, resolve } from "node:path"
|
|
35
35
|
import { fileURLToPath } from "node:url"
|
|
36
36
|
import { ignoreGlob, lintText, makeRules } from "./lint-rules.mjs"
|
|
37
|
-
import { labelContrastScanner } from "./label-contrast.mjs"
|
|
37
|
+
import { labelContrastScanner, statusTextDrift } from "./label-contrast.mjs"
|
|
38
38
|
import { skeletonize } from "./skeleton.mjs"
|
|
39
39
|
import { assessReview, reviewTemplate } from "./review-evidence.mjs"
|
|
40
40
|
|
|
@@ -109,7 +109,7 @@ const motionTokens = new Set(
|
|
|
109
109
|
REGISTRY.tokens?.motion?.scale,
|
|
110
110
|
].filter(Boolean),
|
|
111
111
|
)
|
|
112
|
-
const RULES = makeRules({ semanticTokens, motionTokens, labelContrast: await labelContrastScanner(PKG_ROOT, REGISTRY.themes?.default) }).filter((r) => r.scope !== "package")
|
|
112
|
+
const RULES = makeRules({ semanticTokens, motionTokens, labelContrast: await labelContrastScanner(PKG_ROOT, REGISTRY.themes?.default), statusText: (css) => statusTextDrift(css, REGISTRY.themes?.default) }).filter((r) => r.scope !== "package")
|
|
113
113
|
const PROP_SURFACE = REGISTRY.propSurface?.items ?? {}
|
|
114
114
|
const ICONS = new Set(REGISTRY.iconContract?.names ?? [])
|
|
115
115
|
/**
|
|
@@ -136,3 +136,39 @@ export async function labelContrastScanner(pkgRoot, defaultTheme = "sable") {
|
|
|
136
136
|
return out
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
/** Status fills and the token a status colour drawn on a surface reads instead. */
|
|
141
|
+
const STATUS_TEXT = { destructive: "destructive-text", success: "success-text", "tone-warning": "tone-warning-text", "tone-info": "tone-info-text" }
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The `status-text-drift` rule's reader. Since 0.6, `text-destructive`,
|
|
145
|
+
* `border-destructive`, `ring-destructive` and the rest read `--destructive-text`;
|
|
146
|
+
* only `bg-destructive` reads the fill. An app that retunes `--destructive` and
|
|
147
|
+
* stops there gets a button in its red and error text, borders and focus rings in
|
|
148
|
+
* the theme's — and nothing on screen says which is which. CSS cannot notice "the
|
|
149
|
+
* fill moved and the text did not", so the stylesheet is read for it: a status
|
|
150
|
+
* fill set, in a theme and mode, with no `-text` set beside it there.
|
|
151
|
+
*
|
|
152
|
+
* Pure: it needs the declarations only, not the built package, so it reports
|
|
153
|
+
* even where `label-contrast` cannot measure.
|
|
154
|
+
*/
|
|
155
|
+
export function statusTextDrift(css, defaultTheme = "sable") {
|
|
156
|
+
const decls = customProperties(stripComments(css))
|
|
157
|
+
const fills = decls.filter((d) => d.name in STATUS_TEXT)
|
|
158
|
+
if (!fills.length) return []
|
|
159
|
+
const themeOf = (d) => d.context.match(THEME)?.[1] ?? defaultTheme
|
|
160
|
+
const out = []
|
|
161
|
+
for (const d of fills) {
|
|
162
|
+
const text = STATUS_TEXT[d.name]
|
|
163
|
+
const missing = ["light", "dark"].filter(
|
|
164
|
+
(mode) => applies(d, mode) && !decls.some((t) => t.name === text && themeOf(t) === themeOf(d) && applies(t, mode)),
|
|
165
|
+
)
|
|
166
|
+
if (!missing.length) continue
|
|
167
|
+
const modes = missing.length === 2 ? "light and dark" : missing[0]
|
|
168
|
+
out.push({
|
|
169
|
+
index: d.index,
|
|
170
|
+
match: `--${d.name} is set without --${text} (${modes}): text-${d.name}, borders and rings keep the theme's colour → set --${text} too`,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
return out
|
|
174
|
+
}
|
package/scripts/lint-rules.mjs
CHANGED
|
@@ -118,12 +118,12 @@ function classTokens(expr) {
|
|
|
118
118
|
/**
|
|
119
119
|
* Build the rule table against a token contract.
|
|
120
120
|
*
|
|
121
|
-
* @param {{ semanticTokens: Set<string>, motionTokens: Set<string>, labelContrast?: ((css: string) => { index: number, match: string }[]) | null }} contract
|
|
122
|
-
* `labelContrast`
|
|
123
|
-
* passed in so this table stays dependency free;
|
|
124
|
-
* listed and reports nothing.
|
|
121
|
+
* @param {{ semanticTokens: Set<string>, motionTokens: Set<string>, labelContrast?: ((css: string) => { index: number, match: string }[]) | null, statusText?: ((css: string) => { index: number, match: string }[]) | null }} contract
|
|
122
|
+
* `labelContrast` and `statusText` are the stylesheet scanners from
|
|
123
|
+
* label-contrast.mjs. They are passed in so this table stays dependency free;
|
|
124
|
+
* without one its rule is listed and reports nothing.
|
|
125
125
|
*/
|
|
126
|
-
export function makeRules({ semanticTokens, motionTokens, labelContrast = null }) {
|
|
126
|
+
export function makeRules({ semanticTokens, motionTokens, labelContrast = null, statusText = null }) {
|
|
127
127
|
// Easing utility names = the theme's motion-ease-* tokens + Tailwind built-ins.
|
|
128
128
|
const easeNames = new Set([
|
|
129
129
|
...[...motionTokens]
|
|
@@ -546,6 +546,22 @@ export function makeRules({ semanticTokens, motionTokens, labelContrast = null }
|
|
|
546
546
|
return ctx?.isStylesheet && labelContrast ? labelContrast(text) : []
|
|
547
547
|
},
|
|
548
548
|
},
|
|
549
|
+
{
|
|
550
|
+
id: "status-text-drift",
|
|
551
|
+
/* CONSUMER-scoped. 0.6 split each status colour into a fill and a
|
|
552
|
+
* `-text`; an app that retunes only the fill paints its buttons in its
|
|
553
|
+
* own red and its error text, borders and rings in the theme's. Nothing
|
|
554
|
+
* on screen says which is which, so the stylesheet is read for it. */
|
|
555
|
+
scope: "consumer",
|
|
556
|
+
since: "0.6.1",
|
|
557
|
+
file: true,
|
|
558
|
+
code: true,
|
|
559
|
+
why:
|
|
560
|
+
"A status colour has two tokens: the fill (`--destructive`, `--success`, `--tone-warning`, `--tone-info`), which `bg-*` paints, and its `-text` (`--destructive-text` …), which `text-*`, `border-*`, `ring-*`, `outline-*`, `fill-*` and `stroke-*` read. Setting the fill alone leaves the text on the theme's colour, so a retuned red button sits beside the theme's red error message. Set the `-text` in the same theme and mode: the fill itself where it already reads on your surfaces, or a step of it moved away from them (lighter in dark mode) so it clears 4.5:1 on a card.",
|
|
561
|
+
scan(text, ctx) {
|
|
562
|
+
return ctx?.isStylesheet && statusText ? statusText(text) : []
|
|
563
|
+
},
|
|
564
|
+
},
|
|
549
565
|
{
|
|
550
566
|
id: "hand-derived-surface",
|
|
551
567
|
/* CONSUMER-scoped. Mixing ink into a surface by hand is what the
|