@tempoxyz/ds 0.2.0 → 0.2.2
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 +36 -0
- package/dist/mobile/components/Avatar.d.ts +1 -1
- package/dist/mobile/components/Avatar.d.ts.map +1 -1
- package/dist/mobile/components/Avatar.js.map +1 -1
- package/dist/mobile/components/Button.d.ts +1 -1
- package/dist/mobile/components/Button.d.ts.map +1 -1
- package/dist/mobile/components/Button.js +5 -1
- package/dist/mobile/components/Button.js.map +1 -1
- package/dist/mobile/components/CodeInput.d.ts +1 -1
- package/dist/mobile/components/CodeInput.d.ts.map +1 -1
- package/dist/mobile/components/CodeInput.js.map +1 -1
- package/dist/mobile/components/IconButton.d.ts +1 -1
- package/dist/mobile/components/IconButton.d.ts.map +1 -1
- package/dist/mobile/components/IconButton.js +5 -1
- package/dist/mobile/components/IconButton.js.map +1 -1
- package/dist/mobile/components/Skeleton.d.ts.map +1 -1
- package/dist/mobile/components/Skeleton.js.map +1 -1
- package/dist/mobile/components/Spinner.d.ts +1 -1
- package/dist/mobile/components/Spinner.d.ts.map +1 -1
- package/dist/mobile/components/Spinner.js.map +1 -1
- package/dist/mobile/components/TextButton.d.ts.map +1 -1
- package/dist/mobile/components/TextButton.js +7 -2
- package/dist/mobile/components/TextButton.js.map +1 -1
- package/dist/mobile/components/TextInput.d.ts.map +1 -1
- package/dist/mobile/components/TextInput.js +1 -1
- package/dist/mobile/components/TextInput.js.map +1 -1
- package/dist/oxlint/index.d.ts +43 -0
- package/dist/oxlint/index.d.ts.map +1 -0
- package/dist/oxlint/index.js +68 -0
- package/dist/oxlint/index.js.map +1 -0
- package/dist/oxlint/internal/Imports.d.ts +22 -0
- package/dist/oxlint/internal/Imports.d.ts.map +1 -0
- package/dist/oxlint/internal/Imports.js +52 -0
- package/dist/oxlint/internal/Imports.js.map +1 -0
- package/dist/oxlint/rules/NoCrossSurface.d.ts +16 -0
- package/dist/oxlint/rules/NoCrossSurface.d.ts.map +1 -0
- package/dist/oxlint/rules/NoCrossSurface.js +55 -0
- package/dist/oxlint/rules/NoCrossSurface.js.map +1 -0
- package/dist/oxlint/rules/NoInternalImports.d.ts +8 -0
- package/dist/oxlint/rules/NoInternalImports.d.ts.map +1 -0
- package/dist/oxlint/rules/NoInternalImports.js +22 -0
- package/dist/oxlint/rules/NoInternalImports.js.map +1 -0
- package/dist/oxlint/rules/NoRawValues.d.ts +9 -0
- package/dist/oxlint/rules/NoRawValues.d.ts.map +1 -0
- package/dist/oxlint/rules/NoRawValues.js +73 -0
- package/dist/oxlint/rules/NoRawValues.js.map +1 -0
- package/dist/oxlint/rules/PreferComponent.d.ts +16 -0
- package/dist/oxlint/rules/PreferComponent.d.ts.map +1 -0
- package/dist/oxlint/rules/PreferComponent.js +92 -0
- package/dist/oxlint/rules/PreferComponent.js.map +1 -0
- package/dist/oxlint/rules/RequireAccessibleName.d.ts +9 -0
- package/dist/oxlint/rules/RequireAccessibleName.d.ts.map +1 -0
- package/dist/oxlint/rules/RequireAccessibleName.js +104 -0
- package/dist/oxlint/rules/RequireAccessibleName.js.map +1 -0
- package/dist/web/components/Amount.d.ts +10 -3
- package/dist/web/components/Amount.d.ts.map +1 -1
- package/dist/web/components/Amount.js +19 -9
- package/dist/web/components/Amount.js.map +1 -1
- package/dist/web/components/AmountInput.d.ts +15 -2
- package/dist/web/components/AmountInput.d.ts.map +1 -1
- package/dist/web/components/AmountInput.js +42 -11
- package/dist/web/components/AmountInput.js.map +1 -1
- package/dist/web/components/AmountInput.js.zyzz.json +1 -1
- package/dist/web/components/Autocomplete.js.zyzz.json +1 -1
- package/dist/web/components/Banner.js +1 -1
- package/dist/web/components/Calendar.d.ts.map +1 -1
- package/dist/web/components/Calendar.js +7 -5
- package/dist/web/components/Calendar.js.map +1 -1
- package/dist/web/components/Callout.d.ts.map +1 -1
- package/dist/web/components/Callout.js +4 -1
- package/dist/web/components/Callout.js.map +1 -1
- package/dist/web/components/Combobox.js.zyzz.json +1 -1
- package/dist/web/components/DatePicker.css +1 -1
- package/dist/web/components/DatePicker.d.ts +1 -1
- package/dist/web/components/DatePicker.js.zyzz.json +1 -1
- package/dist/web/components/Dialog.js +2 -2
- package/dist/web/components/Dialog.js.map +1 -1
- package/dist/web/components/Drawer.js +2 -2
- package/dist/web/components/Drawer.js.map +1 -1
- package/dist/web/components/Field.d.ts +7 -0
- package/dist/web/components/Field.d.ts.map +1 -1
- package/dist/web/components/Field.js +25 -3
- package/dist/web/components/Field.js.map +1 -1
- package/dist/web/components/Input.css +1 -1
- package/dist/web/components/Input.js.zyzz.json +1 -1
- package/dist/web/components/Kbd.d.ts +31 -9
- package/dist/web/components/Kbd.d.ts.map +1 -1
- package/dist/web/components/Kbd.js +55 -13
- package/dist/web/components/Kbd.js.map +1 -1
- package/dist/web/components/Menu.css +1 -1
- package/dist/web/components/Menu.js +1 -1
- package/dist/web/components/Menu.js.zyzz.json +1 -1
- package/dist/web/components/NumberField.css +1 -1
- package/dist/web/components/NumberField.js.zyzz.json +1 -1
- package/dist/web/components/Pagination.css +1 -1
- package/dist/web/components/Pagination.d.ts +1 -1
- package/dist/web/components/Pagination.js.zyzz.json +1 -1
- package/dist/web/components/Rows.css +1 -1
- package/dist/web/components/Rows.d.ts +1 -1
- package/dist/web/components/Rows.d.ts.map +1 -1
- package/dist/web/components/Rows.js +3 -3
- package/dist/web/components/Rows.js.map +1 -1
- package/dist/web/components/Rows.js.zyzz.json +1 -1
- package/dist/web/components/Select.css +1 -1
- package/dist/web/components/Select.js.zyzz.json +1 -1
- package/dist/web/components/Snippet.css +1 -1
- package/dist/web/components/Snippet.js +2 -2
- package/dist/web/components/Snippet.js.zyzz.json +1 -1
- package/dist/web/components/Table.css +1 -1
- package/dist/web/components/Table.d.ts +6 -3
- package/dist/web/components/Table.d.ts.map +1 -1
- package/dist/web/components/Table.js +5 -2
- package/dist/web/components/Table.js.map +1 -1
- package/dist/web/components/Table.js.zyzz.json +1 -1
- package/dist/web/components/Textarea.css +1 -1
- package/dist/web/components/Textarea.d.ts +1 -1
- package/dist/web/components/Textarea.js.zyzz.json +1 -1
- package/dist/web/components/Toast.d.ts.map +1 -1
- package/dist/web/components/Toast.js +5 -1
- package/dist/web/components/Toast.js.map +1 -1
- package/dist/web/components/internal/Field.css +1 -1
- package/dist/web/components/internal/Field.js.zyzz.json +1 -1
- package/dist/web/components/internal/InputGroup.d.ts.map +1 -1
- package/dist/web/components/internal/InputGroup.js.map +1 -1
- package/dist/web/components/internal/Listbox.css +1 -1
- package/dist/web/components/internal/Listbox.js.zyzz.json +1 -1
- package/dist/web/icons/BadgeX.d.ts.map +1 -1
- package/dist/web/icons/BadgeX.js.map +1 -1
- package/dist/web/icons/InfinityIcon.d.ts.map +1 -1
- package/dist/web/icons/InfinityIcon.js.map +1 -1
- package/dist/web/icons/LightMode.d.ts.map +1 -1
- package/dist/web/icons/LightMode.js.map +1 -1
- package/dist/web/icons/OctagonAlert.d.ts.map +1 -1
- package/dist/web/icons/OctagonAlert.js.map +1 -1
- package/dist/web/icons/Unlink.d.ts.map +1 -1
- package/dist/web/icons/Unlink.js.map +1 -1
- package/dist/web/index.js.zyzz.json +1 -1
- package/package.json +7 -1
- package/src/mobile/components/Avatar.tsx +2 -2
- package/src/mobile/components/Button.tsx +5 -2
- package/src/mobile/components/CodeInput.tsx +2 -2
- package/src/mobile/components/IconButton.tsx +5 -2
- package/src/mobile/components/Skeleton.tsx +1 -1
- package/src/mobile/components/Spinner.tsx +1 -1
- package/src/mobile/components/TextButton.tsx +6 -2
- package/src/mobile/components/TextInput.tsx +1 -1
- package/src/oxlint/index.ts +89 -0
- package/src/oxlint/internal/Imports.ts +68 -0
- package/src/oxlint/rules/NoCrossSurface.ts +71 -0
- package/src/oxlint/rules/NoInternalImports.ts +25 -0
- package/src/oxlint/rules/NoRawValues.ts +76 -0
- package/src/oxlint/rules/PreferComponent.ts +113 -0
- package/src/oxlint/rules/RequireAccessibleName.ts +132 -0
- package/src/web/components/Amount.tsx +29 -9
- package/src/web/components/AmountInput.tsx +48 -12
- package/src/web/components/Banner.tsx +1 -1
- package/src/web/components/Calendar.tsx +5 -6
- package/src/web/components/Callout.tsx +4 -1
- package/src/web/components/DatePicker.tsx +1 -1
- package/src/web/components/Dialog.tsx +2 -2
- package/src/web/components/Drawer.tsx +2 -2
- package/src/web/components/Field.tsx +37 -2
- package/src/web/components/Input.tsx +1 -1
- package/src/web/components/Kbd.tsx +76 -20
- package/src/web/components/Menu.tsx +2 -2
- package/src/web/components/NumberField.tsx +1 -1
- package/src/web/components/Pagination.tsx +1 -1
- package/src/web/components/Rows.tsx +1 -2
- package/src/web/components/Select.tsx +1 -1
- package/src/web/components/Snippet.tsx +1 -1
- package/src/web/components/Table.tsx +7 -3
- package/src/web/components/Textarea.tsx +1 -1
- package/src/web/components/Toast.tsx +5 -1
- package/src/web/components/internal/Field.ts +1 -1
- package/src/web/components/internal/InputGroup.ts +1 -1
- package/src/web/components/internal/Listbox.ts +2 -2
- package/src/web/icons/BadgeX.tsx +1 -1
- package/src/web/icons/InfinityIcon.tsx +1 -1
- package/src/web/icons/LightMode.tsx +1 -1
- package/src/web/icons/OctagonAlert.tsx +1 -1
- package/src/web/icons/Unlink.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tempoxyz/ds",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
7
7
|
"DESIGN.md",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"description": "Tempo Design System",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@base-ui/react": "^1.8.0",
|
|
19
|
+
"@oxlint/plugins": "1.79.0",
|
|
19
20
|
"cuer": "0.0.3",
|
|
20
21
|
"midcut": "0.2.0",
|
|
21
22
|
"torph": "0.1.3"
|
|
@@ -1262,6 +1263,11 @@
|
|
|
1262
1263
|
"types": "./dist/mobile/icons/index.d.ts",
|
|
1263
1264
|
"react-native": "./src/mobile/icons/index.ts",
|
|
1264
1265
|
"default": "./dist/mobile/icons/index.js"
|
|
1266
|
+
},
|
|
1267
|
+
"./oxlint": {
|
|
1268
|
+
"src": "./src/oxlint/index.ts",
|
|
1269
|
+
"types": "./dist/oxlint/index.d.ts",
|
|
1270
|
+
"default": "./dist/oxlint/index.js"
|
|
1265
1271
|
}
|
|
1266
1272
|
},
|
|
1267
1273
|
"peerDependencies": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Image, Text, View
|
|
1
|
+
import { type ComponentPropsWithRef, useState } from 'react'
|
|
2
|
+
import { Image, type ImageSourcePropType, Text, View } from 'react-native'
|
|
3
3
|
import type * as zyzz from 'zyzz'
|
|
4
4
|
import { style, variants } from '../zyzz.config.js'
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Haptics from 'expo-haptics'
|
|
2
2
|
import { cloneElement, type ComponentPropsWithRef, type ReactElement } from 'react'
|
|
3
|
-
import { Pressable, Text, View
|
|
3
|
+
import { type ColorValue, Pressable, Text, View } from 'react-native'
|
|
4
4
|
import type * as zyzz from 'zyzz'
|
|
5
5
|
import { useColor } from '../internal/useColor.js'
|
|
6
6
|
import { inverse } from '../vars.js'
|
|
@@ -58,7 +58,10 @@ export function Button(props: Button.Props) {
|
|
|
58
58
|
styles.button({
|
|
59
59
|
appearance,
|
|
60
60
|
scale,
|
|
61
|
-
state:
|
|
61
|
+
state: (() => {
|
|
62
|
+
if (disabled) return 'disabled'
|
|
63
|
+
return state.pressed ? 'pressed' : undefined
|
|
64
|
+
})(),
|
|
62
65
|
variant,
|
|
63
66
|
}).style,
|
|
64
67
|
typeof props.style === 'function' ? props.style(state) : props.style,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useImperativeHandle, useRef, useState
|
|
2
|
-
import { Keyboard, Text, TextInput, View, type
|
|
1
|
+
import { type ComponentPropsWithRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
|
2
|
+
import { Keyboard, type StyleProp, Text, TextInput, View, type ViewStyle } from 'react-native'
|
|
3
3
|
import type * as zyzz from 'zyzz'
|
|
4
4
|
import { style, variants } from '../zyzz.config.js'
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Haptics from 'expo-haptics'
|
|
2
2
|
import { cloneElement, type ComponentPropsWithRef, type ReactElement } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { type ColorValue, Pressable } from 'react-native'
|
|
4
4
|
import type * as zyzz from 'zyzz'
|
|
5
5
|
import { useColor } from '../internal/useColor.js'
|
|
6
6
|
import { inverse } from '../vars.js'
|
|
@@ -46,7 +46,10 @@ export function IconButton(props: IconButton.Props) {
|
|
|
46
46
|
styles.button({
|
|
47
47
|
appearance,
|
|
48
48
|
scale,
|
|
49
|
-
state:
|
|
49
|
+
state: (() => {
|
|
50
|
+
if (disabled) return 'disabled'
|
|
51
|
+
return state.pressed ? 'pressed' : undefined
|
|
52
|
+
})(),
|
|
50
53
|
variant,
|
|
51
54
|
}).style,
|
|
52
55
|
typeof props.style === 'function' ? props.style(state) : props.style,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithRef } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { type DimensionValue, View } from 'react-native'
|
|
3
3
|
import Animated from 'react-native-reanimated'
|
|
4
4
|
import type * as zyzz from 'zyzz'
|
|
5
5
|
import { useReducedMotion } from '../internal/useReducedMotion.js'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithRef } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { type ColorValue, View } from 'react-native'
|
|
3
3
|
import Animated from 'react-native-reanimated'
|
|
4
4
|
import { Circle, Path, Svg } from 'react-native-svg'
|
|
5
5
|
import type * as zyzz from 'zyzz'
|
|
@@ -39,8 +39,12 @@ export function TextButton(props: TextButton.Props) {
|
|
|
39
39
|
props.onPress?.(event)
|
|
40
40
|
}}
|
|
41
41
|
style={(state) => [
|
|
42
|
-
styles.button({
|
|
43
|
-
|
|
42
|
+
styles.button({
|
|
43
|
+
state: (() => {
|
|
44
|
+
if (disabled) return 'disabled'
|
|
45
|
+
return state.pressed ? 'pressed' : undefined
|
|
46
|
+
})(),
|
|
47
|
+
}).style,
|
|
44
48
|
typeof props.style === 'function' ? props.style(state) : props.style,
|
|
45
49
|
]}
|
|
46
50
|
>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithRef } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { TextInput as NativeTextInput, Text, View } from 'react-native'
|
|
3
3
|
import type * as zyzz from 'zyzz'
|
|
4
4
|
import { useColor } from '../internal/useColor.js'
|
|
5
5
|
import { style, variants } from '../zyzz.config.js'
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** Oxlint plugin and configurations that check how an app uses `@tempoxyz/ds`. @module */
|
|
2
|
+
import type { Plugin } from '@oxlint/plugins'
|
|
3
|
+
import * as NoCrossSurface from './rules/NoCrossSurface.ts'
|
|
4
|
+
import * as NoInternalImports from './rules/NoInternalImports.ts'
|
|
5
|
+
import * as NoRawValues from './rules/NoRawValues.ts'
|
|
6
|
+
import * as PreferComponent from './rules/PreferComponent.ts'
|
|
7
|
+
import * as RequireAccessibleName from './rules/RequireAccessibleName.ts'
|
|
8
|
+
|
|
9
|
+
/** An Oxlint rule severity. */
|
|
10
|
+
export type Severity = 'error' | 'off' | 'warn'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A lint configuration that Oxlint and the `lint` option of Vite Plus accept. Arrays stay mutable
|
|
14
|
+
* because Oxlint's configuration type declares them mutable.
|
|
15
|
+
*/
|
|
16
|
+
export type Config = {
|
|
17
|
+
/** Plugins to load, keyed by the name that prefixes their rules. */
|
|
18
|
+
jsPlugins: { name: string; specifier: string }[]
|
|
19
|
+
/** Rule severities, with options as the second tuple member. */
|
|
20
|
+
rules: Record<string, Severity | [Severity, ...unknown[]]>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const plugins: Config['jsPlugins'] = [
|
|
24
|
+
{ name: 'tds', specifier: '@tempoxyz/ds/oxlint' },
|
|
25
|
+
{ name: 'zyzz', specifier: 'zyzz/oxlint' },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const zyzz: Config['rules'] = {
|
|
29
|
+
'zyzz/no-conflicting-props': 'error',
|
|
30
|
+
'zyzz/no-unused': 'warn',
|
|
31
|
+
'zyzz/valid-styles': 'error',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Enables the TDS and Zyzz rules for an app that renders `@tempoxyz/ds/mobile`.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { mobile } from '@tempoxyz/ds/oxlint'
|
|
40
|
+
*
|
|
41
|
+
* export default defineConfig({ lint: mobile })
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export const mobile: Config = {
|
|
45
|
+
jsPlugins: plugins,
|
|
46
|
+
rules: {
|
|
47
|
+
'tds/no-cross-surface': ['error', { surface: 'mobile' }],
|
|
48
|
+
'tds/no-internal-imports': 'error',
|
|
49
|
+
'tds/no-raw-values': 'warn',
|
|
50
|
+
'tds/require-accessible-name': 'error',
|
|
51
|
+
...zyzz,
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Enables the TDS and Zyzz rules for an app that renders `@tempoxyz/ds`.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { web } from '@tempoxyz/ds/oxlint'
|
|
61
|
+
*
|
|
62
|
+
* export default defineConfig({ lint: web })
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export const web: Config = {
|
|
66
|
+
jsPlugins: plugins,
|
|
67
|
+
rules: {
|
|
68
|
+
'tds/no-cross-surface': ['error', { surface: 'web' }],
|
|
69
|
+
'tds/no-internal-imports': 'error',
|
|
70
|
+
'tds/no-raw-values': 'warn',
|
|
71
|
+
'tds/prefer-component': 'warn',
|
|
72
|
+
'tds/require-accessible-name': 'error',
|
|
73
|
+
...zyzz,
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Register through `jsPlugins` as `tds`, then enable `tds/*` rules or spread `web` or `mobile`. */
|
|
78
|
+
const plugin: Plugin = {
|
|
79
|
+
meta: { name: 'tds' },
|
|
80
|
+
rules: {
|
|
81
|
+
'no-cross-surface': NoCrossSurface.rule,
|
|
82
|
+
'no-internal-imports': NoInternalImports.rule,
|
|
83
|
+
'no-raw-values': NoRawValues.rule,
|
|
84
|
+
'prefer-component': PreferComponent.rule,
|
|
85
|
+
'require-accessible-name': RequireAccessibleName.rule,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default plugin
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Classifies `@tempoxyz/ds` import specifiers and visits every static module reference in a file. @module */
|
|
2
|
+
import type { ESTree, Visitor } from '@oxlint/plugins'
|
|
3
|
+
import * as Module from 'node:module'
|
|
4
|
+
|
|
5
|
+
/** A library surface, named after the entrypoint that ships it. */
|
|
6
|
+
export type Surface = 'mobile' | 'web'
|
|
7
|
+
|
|
8
|
+
// The manifest sits three levels up from both `src/oxlint/internal` and `dist/oxlint/internal`.
|
|
9
|
+
const manifest: { exports: Record<string, unknown>; name: string } = Module.createRequire(
|
|
10
|
+
import.meta.url,
|
|
11
|
+
)('../../../package.json')
|
|
12
|
+
|
|
13
|
+
/** The published package name, `@tempoxyz/ds`. */
|
|
14
|
+
export const name = manifest.name
|
|
15
|
+
|
|
16
|
+
/** Returns whether `source` names the package or one of its subpaths. */
|
|
17
|
+
export function owned(source: string): boolean {
|
|
18
|
+
return source === name || source.startsWith(`${name}/`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Returns whether `source` resolves through a subpath that the package `exports` map publishes. */
|
|
22
|
+
export function published(source: string): boolean {
|
|
23
|
+
return owned(source) && Object.hasOwn(manifest.exports, `.${source.slice(name.length)}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Returns the surface a published specifier belongs to, or `undefined` for tooling entrypoints. */
|
|
27
|
+
export function surface(source: string): Surface | undefined {
|
|
28
|
+
if (!published(source)) return undefined
|
|
29
|
+
|
|
30
|
+
const subpath = source.slice(name.length)
|
|
31
|
+
|
|
32
|
+
if (subpath === '/mobile' || subpath.startsWith('/mobile/')) return 'mobile'
|
|
33
|
+
if (subpath === '' || subpath.startsWith('/fonts/') || subpath.startsWith('/icons')) return 'web'
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns a visitor that calls `fn` with each string specifier in an import, a re-export, an
|
|
39
|
+
* `import()` expression, or an `import()` type.
|
|
40
|
+
*/
|
|
41
|
+
export function visit(fn: visit.Callback): Visitor {
|
|
42
|
+
function literal(node: ESTree.Node | null | undefined) {
|
|
43
|
+
if (node?.type === 'Literal' && typeof node.value === 'string') fn(node.value, node)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
ExportAllDeclaration(node) {
|
|
48
|
+
literal(node.source)
|
|
49
|
+
},
|
|
50
|
+
ExportNamedDeclaration(node) {
|
|
51
|
+
literal(node.source)
|
|
52
|
+
},
|
|
53
|
+
ImportDeclaration(node) {
|
|
54
|
+
literal(node.source)
|
|
55
|
+
},
|
|
56
|
+
ImportExpression(node) {
|
|
57
|
+
literal(node.source)
|
|
58
|
+
},
|
|
59
|
+
TSImportType(node) {
|
|
60
|
+
literal(node.source)
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export declare namespace visit {
|
|
66
|
+
/** Receives the specifier and the string literal that spells it. */
|
|
67
|
+
type Callback = (source: string, node: ESTree.Node) => void
|
|
68
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Keeps a file, or a whole app, on one `@tempoxyz/ds` surface. @module */
|
|
2
|
+
import type { ESTree, Rule } from '@oxlint/plugins'
|
|
3
|
+
import * as Imports from '../internal/Imports.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reports an import from the other surface. With `surface` set, every import from the other surface
|
|
7
|
+
* is reported. Without it, the rule reports a file that imports from both surfaces.
|
|
8
|
+
*/
|
|
9
|
+
export const rule: Rule = {
|
|
10
|
+
meta: {
|
|
11
|
+
docs: { description: 'Keep an app on one `@tempoxyz/ds` surface.' },
|
|
12
|
+
messages: {
|
|
13
|
+
configured:
|
|
14
|
+
"'{{source}}' belongs to the {{found}} surface, but this app uses the {{surface}} surface.",
|
|
15
|
+
mixed:
|
|
16
|
+
"'{{source}}' belongs to the {{found}} surface, but this file imports the {{surface}} surface.",
|
|
17
|
+
},
|
|
18
|
+
schema: [
|
|
19
|
+
{
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
properties: { surface: { enum: ['mobile', 'web'], type: 'string' } },
|
|
22
|
+
type: 'object',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
type: 'problem',
|
|
26
|
+
},
|
|
27
|
+
create(context) {
|
|
28
|
+
const options = context.options[0] as rule.Options | undefined
|
|
29
|
+
|
|
30
|
+
type Reference = { node: ESTree.Node; source: string; surface: Imports.Surface }
|
|
31
|
+
|
|
32
|
+
const references: Reference[] = []
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
...Imports.visit((source, node) => {
|
|
36
|
+
const surface = Imports.surface(source)
|
|
37
|
+
|
|
38
|
+
if (!surface) return
|
|
39
|
+
if (options?.surface && surface !== options.surface)
|
|
40
|
+
context.report({
|
|
41
|
+
data: { found: surface, source, surface: options.surface },
|
|
42
|
+
messageId: 'configured',
|
|
43
|
+
node,
|
|
44
|
+
})
|
|
45
|
+
references.push({ node, source, surface })
|
|
46
|
+
}),
|
|
47
|
+
'Program:exit'() {
|
|
48
|
+
if (options?.surface) return
|
|
49
|
+
|
|
50
|
+
const first = references[0]
|
|
51
|
+
|
|
52
|
+
if (!first) return
|
|
53
|
+
for (const reference of references)
|
|
54
|
+
if (reference.surface !== first.surface)
|
|
55
|
+
context.report({
|
|
56
|
+
data: { found: reference.surface, source: reference.source, surface: first.surface },
|
|
57
|
+
messageId: 'mixed',
|
|
58
|
+
node: reference.node,
|
|
59
|
+
})
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export declare namespace rule {
|
|
66
|
+
/** Options for `tds/no-cross-surface`. */
|
|
67
|
+
type Options = {
|
|
68
|
+
/** The surface the app renders. When absent, the rule checks each file on its own. */
|
|
69
|
+
surface?: Imports.Surface | undefined
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Restricts `@tempoxyz/ds` imports to the subpaths that the package publishes. @module */
|
|
2
|
+
import type { Rule } from '@oxlint/plugins'
|
|
3
|
+
import * as Imports from '../internal/Imports.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reports a `@tempoxyz/ds` specifier that the package `exports` map does not publish, such as
|
|
7
|
+
* `@tempoxyz/ds/src/web/components/Button`. Internal paths change without a release note.
|
|
8
|
+
*/
|
|
9
|
+
export const rule: Rule = {
|
|
10
|
+
meta: {
|
|
11
|
+
docs: { description: 'Allow only the published `@tempoxyz/ds` entrypoints.' },
|
|
12
|
+
messages: {
|
|
13
|
+
internal:
|
|
14
|
+
"'{{source}}' is not a published entrypoint. Import from '@tempoxyz/ds', '@tempoxyz/ds/mobile', or their `icons` subpaths.",
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
type: 'problem',
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
return Imports.visit((source, node) => {
|
|
21
|
+
if (Imports.owned(source) && !Imports.published(source))
|
|
22
|
+
context.report({ data: { source }, messageId: 'internal', node })
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Finds raw colors, lengths, and font stacks in inline `style` props. @module */
|
|
2
|
+
import type { ESTree, Rule } from '@oxlint/plugins'
|
|
3
|
+
|
|
4
|
+
const color = /#[\da-f]{3,8}\b|\b(?:color|hsla?|hwb|lab|lch|oklab|oklch|rgba?)\(/i
|
|
5
|
+
const length = /(?:^|[\s(,])-?(?:\d*\.)?\d+(?:em|pt|px|rem)\b/i
|
|
6
|
+
|
|
7
|
+
// React and React Native read a bare number on these properties as pixels or points.
|
|
8
|
+
const lengths =
|
|
9
|
+
/^(?:bottom|columnGap|end|flexBasis|fontSize|gap|height|inset\w*|left|margin\w*|maxHeight|maxWidth|minHeight|minWidth|padding\w*|right|rowGap|start|top|width|\w*Radius|\w*Width)$/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Reports a color, length, or font family written as a literal in a JSX `style` prop. Values that
|
|
13
|
+
* read a custom property through `var()` pass, since they resolve to a token. Style values
|
|
14
|
+
* inside `style()` and `variants()` calls belong to `zyzz/valid-styles`.
|
|
15
|
+
*/
|
|
16
|
+
export const rule: Rule = {
|
|
17
|
+
meta: {
|
|
18
|
+
docs: { description: 'Use design tokens instead of raw values in inline styles.' },
|
|
19
|
+
messages: {
|
|
20
|
+
raw: "'{{property}}' sets the raw value {{value}}. Use a token through a Zyzz style instead.",
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
type: 'suggestion',
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
function raw(property: string, node: ESTree.Node) {
|
|
27
|
+
if (node.type === 'UnaryExpression' && node.operator === '-')
|
|
28
|
+
return raw(property, node.argument)
|
|
29
|
+
if (node.type === 'Literal' && typeof node.value === 'number')
|
|
30
|
+
return node.value !== 0 && lengths.test(property)
|
|
31
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0)
|
|
32
|
+
return text(property, node.quasis.map((quasi) => quasi.value.cooked ?? '').join(''))
|
|
33
|
+
if (node.type === 'Literal' && typeof node.value === 'string')
|
|
34
|
+
return text(property, node.value)
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function text(property: string, value: string) {
|
|
39
|
+
if (value.includes('var(')) return false
|
|
40
|
+
return property === 'fontFamily' || color.test(value) || length.test(value)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function check(node: ESTree.Node) {
|
|
44
|
+
if (node.type !== 'ObjectExpression') return
|
|
45
|
+
for (const property of node.properties) {
|
|
46
|
+
if (property.type !== 'Property' || property.computed) continue
|
|
47
|
+
|
|
48
|
+
const key = (() => {
|
|
49
|
+
if (property.key.type === 'Identifier') return property.key.name
|
|
50
|
+
if (property.key.type === 'Literal') return String(property.key.value)
|
|
51
|
+
return undefined
|
|
52
|
+
})()
|
|
53
|
+
|
|
54
|
+
if (key !== undefined && raw(key, property.value))
|
|
55
|
+
context.report({
|
|
56
|
+
data: { property: key, value: context.sourceCode.getText(property.value) },
|
|
57
|
+
messageId: 'raw',
|
|
58
|
+
node: property.value,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
JSXAttribute(node) {
|
|
65
|
+
if (node.name.type !== 'JSXIdentifier' || node.name.name !== 'style') return
|
|
66
|
+
if (node.value?.type !== 'JSXExpressionContainer') return
|
|
67
|
+
|
|
68
|
+
const expression = node.value.expression
|
|
69
|
+
|
|
70
|
+
if (expression.type === 'ArrayExpression')
|
|
71
|
+
for (const element of expression.elements) if (element) check(element)
|
|
72
|
+
check(expression)
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** Points native HTML controls and structures at the Web components that replace them. @module */
|
|
2
|
+
import type { ESTree, Rule } from '@oxlint/plugins'
|
|
3
|
+
|
|
4
|
+
// Each element maps to the component that renders it with Tempo styles, states, and semantics.
|
|
5
|
+
const elements: Record<string, string> = {
|
|
6
|
+
button: 'Button',
|
|
7
|
+
details: 'Accordion',
|
|
8
|
+
dialog: 'Dialog',
|
|
9
|
+
fieldset: 'Fieldset',
|
|
10
|
+
hr: 'Separator',
|
|
11
|
+
kbd: 'Kbd',
|
|
12
|
+
meter: 'Gauge',
|
|
13
|
+
progress: 'Progress',
|
|
14
|
+
select: 'Select',
|
|
15
|
+
table: 'Table',
|
|
16
|
+
textarea: 'Textarea',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Input types without an entry, such as `file`, `hidden`, and `color`, have no replacement.
|
|
20
|
+
const inputs: Record<string, string> = {
|
|
21
|
+
button: 'Button',
|
|
22
|
+
checkbox: 'Checkbox',
|
|
23
|
+
date: 'DatePicker',
|
|
24
|
+
email: 'Input',
|
|
25
|
+
number: 'NumberField',
|
|
26
|
+
password: 'Input',
|
|
27
|
+
radio: 'RadioGroup',
|
|
28
|
+
range: 'Slider',
|
|
29
|
+
reset: 'Button',
|
|
30
|
+
search: 'Input',
|
|
31
|
+
submit: 'Button',
|
|
32
|
+
tel: 'Input',
|
|
33
|
+
text: 'Input',
|
|
34
|
+
url: 'Input',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reports a native element that a `@tempoxyz/ds` component replaces, such as `<button>` or
|
|
39
|
+
* `<input type="checkbox">`. Elements inside a `render` prop are skipped, since the component that
|
|
40
|
+
* receives them supplies the styles.
|
|
41
|
+
*/
|
|
42
|
+
export const rule: Rule = {
|
|
43
|
+
meta: {
|
|
44
|
+
docs: {
|
|
45
|
+
description: 'Prefer `@tempoxyz/ds` components over the native elements they replace.',
|
|
46
|
+
},
|
|
47
|
+
messages: {
|
|
48
|
+
component: "Use '{{component}}' from '@tempoxyz/ds' instead of <{{element}}>.",
|
|
49
|
+
},
|
|
50
|
+
schema: [
|
|
51
|
+
{
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
properties: { allow: { items: { type: 'string' }, type: 'array' } },
|
|
54
|
+
type: 'object',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
type: 'suggestion',
|
|
58
|
+
},
|
|
59
|
+
create(context) {
|
|
60
|
+
const options = context.options[0] as rule.Options | undefined
|
|
61
|
+
const allow = new Set(options?.allow)
|
|
62
|
+
|
|
63
|
+
function component(node: ESTree.JSXOpeningElement) {
|
|
64
|
+
if (node.name.type !== 'JSXIdentifier') return undefined
|
|
65
|
+
|
|
66
|
+
const element = node.name.name
|
|
67
|
+
|
|
68
|
+
if (element !== 'input') return elements[element]
|
|
69
|
+
|
|
70
|
+
const type = node.attributes.find(
|
|
71
|
+
(attribute) =>
|
|
72
|
+
attribute.type === 'JSXAttribute' &&
|
|
73
|
+
attribute.name.type === 'JSXIdentifier' &&
|
|
74
|
+
attribute.name.name === 'type',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if (!type) return inputs.text
|
|
78
|
+
if (type.type !== 'JSXAttribute' || type.value?.type !== 'Literal') return undefined
|
|
79
|
+
return typeof type.value.value === 'string' ? inputs[type.value.value] : undefined
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
JSXOpeningElement(node) {
|
|
84
|
+
if (node.name.type !== 'JSXIdentifier' || allow.has(node.name.name)) return
|
|
85
|
+
|
|
86
|
+
for (let parent: ESTree.Node | null = node.parent; parent; parent = parent.parent)
|
|
87
|
+
if (
|
|
88
|
+
parent.type === 'JSXAttribute' &&
|
|
89
|
+
parent.name.type === 'JSXIdentifier' &&
|
|
90
|
+
parent.name.name === 'render'
|
|
91
|
+
)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
const replacement = component(node)
|
|
95
|
+
|
|
96
|
+
if (replacement)
|
|
97
|
+
context.report({
|
|
98
|
+
data: { component: replacement, element: node.name.name },
|
|
99
|
+
messageId: 'component',
|
|
100
|
+
node,
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export declare namespace rule {
|
|
108
|
+
/** Options for `tds/prefer-component`. */
|
|
109
|
+
type Options = {
|
|
110
|
+
/** Element names to accept, such as `['table']` for a page that renders raw data. */
|
|
111
|
+
allow?: readonly string[] | undefined
|
|
112
|
+
}
|
|
113
|
+
}
|