eslint-plugin-smarthr 6.15.0 → 6.16.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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [6.16.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.15.0...eslint-plugin-smarthr-v6.16.0) (2026-05-19)
6
+
7
+
8
+ ### Features
9
+
10
+ * design-system-guideline-bulk-action-row-button のlinterルールを追加 ([#1309](https://github.com/kufu/tamatebako/issues/1309)) ([d5934ae](https://github.com/kufu/tamatebako/commit/d5934ae3cc95ef9cb5696c00f22840351b5567ea))
11
+ * InformationPanelを白背景に配置することを禁止するルールを追加 ([#1319](https://github.com/kufu/tamatebako/issues/1319)) ([6b3a9b6](https://github.com/kufu/tamatebako/commit/6b3a9b621956e196eb9fb5984e3acba458893eca))
12
+
5
13
  ## [6.15.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.14.0...eslint-plugin-smarthr-v6.15.0) (2026-05-17)
6
14
 
7
15
 
package/README.md CHANGED
@@ -21,8 +21,10 @@
21
21
  - [a11y-trigger-has-button](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-trigger-has-button)
22
22
  - [best-practice-for-async-current-target](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-async-current-target)
23
23
  - [best-practice-for-button-element](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-button-element)
24
+ - [best-practice-for-consecutive-definition-list](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-consecutive-definition-list)
24
25
  - [best-practice-for-data-test-attribute](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-data-test-attribute)
25
26
  - [best-practice-for-date](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-date)
27
+ - [best-practice-for-default-props](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-default-props)
26
28
  - [best-practice-for-interactive-element](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-interactive-element)
27
29
  - [best-practice-for-layouts](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts)
28
30
  - [best-practice-for-nested-attributes-array-index](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-nested-attributes-array-index)
@@ -37,8 +39,10 @@
37
39
  - [best-practice-for-text-component](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-text-component)
38
40
  - [best-practice-for-unnesessary-early-return](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-unnesessary-early-return)
39
41
  - [component-name](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/component-name)
42
+ - [design-system-guideline-bulk-action-row-button](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-bulk-action-row-button)
40
43
  - [design-system-guideline-prohibit-dialog-button-icon](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-prohibit-dialog-button-icon)
41
44
  - [design-system-guideline-prohibit-double-icons](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-prohibit-double-icons)
45
+ - [design-system-guideline-prohibit-information-panel-in-white-bg](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-prohibit-information-panel-in-white-bg)
42
46
  - [format-import-path](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-import-path)
43
47
  - [format-translate-component](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-translate-component)
44
48
  - [no-import-other-domain](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/no-import-other-domain)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "6.15.0",
3
+ "version": "6.16.0",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -2,30 +2,22 @@
2
2
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
3
3
  */
4
4
 
5
- const DEFINITION_LIST_PATTERN = /DefinitionList$/
6
-
7
- const getPreviousSibling = (node) => {
8
- const parent = node.parent
9
- if (!parent || (parent.type !== 'JSXElement' && parent.type !== 'JSXFragment')) {
10
- return null
11
- }
12
-
13
- const children = parent.children || []
14
- const currentIndex = children.indexOf(node)
15
-
16
- for (let i = currentIndex - 1; i >= 0; i--) {
17
- const child = children[i]
18
-
19
- if (
20
- (child.type !== 'JSXText' || child.value.trim() !== '') &&
21
- (child.type !== 'JSXExpressionContainer' || child.expression.type !== 'JSXEmptyExpression')
22
- ) {
23
- return child
24
- }
25
- }
26
-
27
- return null
28
- }
5
+ const DEFINITION_LIST = 'JSXElement[openingElement.name.name=/DefinitionList$/]'
6
+ const WHITESPACE_TEXT = 'JSXText[value=/^\\s*$/]'
7
+ const EMPTY_EXPRESSION = 'JSXExpressionContainer[expression.type="JSXEmptyExpression"]'
8
+
9
+ // 直接隣接(A + B)
10
+ const ADJACENT_DIRECT = `${DEFINITION_LIST} + ${DEFINITION_LIST}`
11
+ // 空白・改行のみのJSXText経由(A + W + B)
12
+ const ADJACENT_WITH_WHITESPACE = `${DEFINITION_LIST} + ${WHITESPACE_TEXT} + ${DEFINITION_LIST}`
13
+ // JSXコメント({/* */}、{})経由(A + E + B)
14
+ const ADJACENT_WITH_COMMENT = `${DEFINITION_LIST} + ${EMPTY_EXPRESSION} + ${DEFINITION_LIST}`
15
+ // 空白・改行 + JSXコメント(A + W + E + B)
16
+ const ADJACENT_WHITESPACE_COMMENT = `${DEFINITION_LIST} + ${WHITESPACE_TEXT} + ${EMPTY_EXPRESSION} + ${DEFINITION_LIST}`
17
+ // JSXコメント + 空白・改行(A + E + W + B)
18
+ const ADJACENT_COMMENT_WHITESPACE = `${DEFINITION_LIST} + ${EMPTY_EXPRESSION} + ${WHITESPACE_TEXT} + ${DEFINITION_LIST}`
19
+ // 空白・改行 + JSXコメント + 空白・改行(A + W + E + W + B)
20
+ const ADJACENT_WHITESPACE_COMMENT_WHITESPACE = `${DEFINITION_LIST} + ${WHITESPACE_TEXT} + ${EMPTY_EXPRESSION} + ${WHITESPACE_TEXT} + ${DEFINITION_LIST}`
29
21
 
30
22
  module.exports = {
31
23
  meta: {
@@ -33,20 +25,23 @@ module.exports = {
33
25
  schema: [],
34
26
  },
35
27
  create(context) {
36
- return {
37
- [`JSXElement[openingElement.name.name=${DEFINITION_LIST_PATTERN}]`](node) {
38
- const prev = getPreviousSibling(node)
39
-
40
- if (prev?.type === 'JSXElement' && DEFINITION_LIST_PATTERN.test(prev.openingElement.name.name)) {
41
- context.report({
42
- node: node.openingElement.name,
43
- message: `DefinitionList が連続しています
28
+ const reporter = (node) => {
29
+ context.report({
30
+ node,
31
+ message: `DefinitionList が連続しています
44
32
  - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-consecutive-definition-list
45
33
  - DefinitionListItem の maxColumns prop を使用して1つにまとめることを検討してください
46
34
  - 例外: 意味的に異なるグループの場合は複数のDefinitionListを使用しても問題ありません`,
47
- })
48
- }
49
- },
35
+ })
36
+ }
37
+
38
+ return {
39
+ [ADJACENT_DIRECT]: reporter,
40
+ [ADJACENT_WITH_WHITESPACE]: reporter,
41
+ [ADJACENT_WITH_COMMENT]: reporter,
42
+ [ADJACENT_WHITESPACE_COMMENT]: reporter,
43
+ [ADJACENT_COMMENT_WHITESPACE]: reporter,
44
+ [ADJACENT_WHITESPACE_COMMENT_WHITESPACE]: reporter,
50
45
  }
51
46
  },
52
47
  }
@@ -0,0 +1,121 @@
1
+ # smarthr/design-system-guideline-bulk-action-row-button
2
+
3
+ BulkActionRow内では「すべてのオブジェクトを選択」ボタンの実装には Button[variant="tertiary"] を使用することを推奨するルールです
4
+
5
+ ## なぜこのルールが必要なのか
6
+
7
+ ### 1. 視覚的なアクセシビリティのため(Design Systemガイドライン)
8
+
9
+ BulkActionRowで使用する「すべてのオブジェクトの選択」ボタンは、Button[variant="tertiary"]を使用することで、BulkActionRowの背景色に対してコントラスト比4.5:1を確保できるように内部的に実装されているため、視覚的に見やすくなります。
10
+
11
+ **参考**:
12
+
13
+ - [テーブルの一括操作 - SmartHR Design System](https://smarthr.design/products/design-patterns/table-bulk-action/#h4-2)
14
+ - [Table - SmartHR Design System](https://smarthr.design/products/components/table/#h3-2)
15
+
16
+ ### 2. セマンティックなマークアップのため
17
+
18
+ BulkActionRowは一括操作用の領域であり、内部に設置するクリッカブルな要素は基本的に**画面遷移以外の動作**(削除、編集、選択など)を担います。
19
+
20
+ - **aタグ、AnchorButton、Link**: 画面遷移(リンク)を示すセマンティックなマークアップ
21
+ - **button要素、Button**: ボタンやその他のインタラクション(フォーム送信、操作実行など)を示すセマンティックなマークアップ
22
+
23
+ BulkActionRow内のクリッカブルな要素は画面遷移を伴わないため、a要素やAnchorButton、Linkでマークアップするのは不適切です。Buttonコンポーネントを使用してください。
24
+
25
+ ### このルールがチェックする内容
26
+
27
+ 1. **aタグやLinkコンポーネントの使用を検出**: BulkActionRow内でaタグやTextLink、末尾がLinkで終わるコンポーネントを使用している場合にエラーを出します
28
+ 2. **prefixが付いたButtonコンポーネントの使用を検出**: BulkActionRow内でAnchorButton、StyledButtonなど、XxxxButtonパターンのコンポーネント(Buttonコンポーネント以外)を使用している場合にエラーを出します
29
+
30
+ ## rules
31
+
32
+ ```js
33
+ {
34
+ rules: {
35
+ 'smarthr/design-system-guideline-bulk-action-row-button': 'error', // 'warn', 'off'
36
+ },
37
+ }
38
+ ```
39
+
40
+ ## ❌ Incorrect
41
+
42
+ ```jsx
43
+ // TextLinkは画面遷移を示すセマンティックなマークアップです
44
+ // BulkActionRowは一括操作用の領域であり、画面遷移以外の動作を担うため、Linkは不適切です
45
+ // また、コントラスト比も確保できません
46
+ <Table>
47
+ <thead>
48
+ <BulkActionRow>
49
+ <Cluster align="center">
50
+ <Text>このページの「オブジェクト名」50件すべて選択されています。</Text>
51
+ <TextLink href={undefined} onClick={toggleAllChecked}>
52
+ 一覧の「オブジェクト名」1000件すべてを選択
53
+ </TextLink>
54
+ </Cluster>
55
+ </BulkActionRow>
56
+ </thead>
57
+ </Table>
58
+ ```
59
+
60
+ ```jsx
61
+ // AnchorButtonはa要素ベースのコンポーネントであり、画面遷移を示すセマンティックなマークアップです
62
+ // BulkActionRowは一括操作用の領域であり、画面遷移以外の動作を担うため、AnchorButtonは不適切です
63
+ <Table>
64
+ <thead>
65
+ <BulkActionRow>
66
+ <Cluster align="center">
67
+ <Text>このページの「オブジェクト名」50件すべて選択されています。</Text>
68
+ <AnchorButton href={undefined} onClick={toggleAllChecked}>
69
+ 一覧の「オブジェクト名」1000件すべてを選択
70
+ </AnchorButton>
71
+ </Cluster>
72
+ </BulkActionRow>
73
+ </thead>
74
+ </Table>
75
+ ```
76
+
77
+ ```jsx
78
+ // prefixが付いたButtonコンポーネントを使用している
79
+ <Table>
80
+ <thead>
81
+ <BulkActionRow>
82
+ <Cluster align="center">
83
+ <Text>このページの「オブジェクト名」50件すべて選択されています。</Text>
84
+ <SelectAllButton>一覧の「オブジェクト名」1000件すべてを選択</SelectAllButton>
85
+ </Cluster>
86
+ </BulkActionRow>
87
+ </thead>
88
+ </Table>
89
+ ```
90
+
91
+ ```jsx
92
+ // prefixが付いたButtonコンポーネントを使用している
93
+ <Table>
94
+ <thead>
95
+ <BulkActionRow>
96
+ <Cluster align="center">
97
+ <Text>このページの「オブジェクト名」50件すべて選択されています。</Text>
98
+ <button>一覧の「オブジェクト名」1000件すべてを選択</button>
99
+ </Cluster>
100
+ </BulkActionRow>
101
+ </thead>
102
+ </Table>
103
+ ```
104
+
105
+ ## ✅ Correct
106
+
107
+ ```jsx
108
+ // 「すべてのオブジェクトの選択」ボタンにvariant="tertiary"を指定
109
+ <Table>
110
+ <thead>
111
+ <BulkActionRow>
112
+ <Cluster align="center">
113
+ <Text>このページの「オブジェクト名」50件すべて選択されています。</Text>
114
+ <Button variant="tertiary" size="S">
115
+ 一覧の「オブジェクト名」1000件すべてを選択
116
+ </Button>
117
+ </Cluster>
118
+ </BulkActionRow>
119
+ </thead>
120
+ </Table>
121
+ ```
@@ -0,0 +1,67 @@
1
+ const SCHEMA = []
2
+
3
+ // aタグやLinkコンポーネントを使用している場合
4
+ const DO_NOT_USE_LINK = `BulkActionRow内では「Button」コンポーネントを使用してください。
5
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-bulk-action-row-button
6
+ - BulkActionRowは一括操作用の領域であり、内部のクリッカブルな要素は画面遷移以外の動作を担います。
7
+ - a要素、AnchorButton、Linkは画面遷移を示すセマンティックなマークアップのため、BulkActionRow内では不適切です。
8
+ - もし「すべてのオブジェクトを選択」ボタンの実装であれば、Button[variant="tertiary"]を使用してください。
9
+ - 参考:
10
+ - https://smarthr.design/products/design-patterns/table-bulk-action/#h4-2
11
+ - https://smarthr.design/products/components/table/#h3-2`
12
+
13
+ // XxxxButtonパターンのコンポーネントを使用している場合
14
+ const DO_NOT_USE_PREFIXED_BUTTON = `BulkActionRow内では「Button」コンポーネントのみを使用してください。
15
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-bulk-action-row-button
16
+ - XxxxButtonのようにprefixが付いたButtonコンポーネント(AnchorButton、StyledButtonなど)は使用しないでください。
17
+ - もし「すべてのオブジェクトを選択」ボタンの実装であれば、Button[variant="tertiary"]を使用してください。
18
+ - 参考:
19
+ - https://smarthr.design/products/design-patterns/table-bulk-action/#h4-2
20
+ - https://smarthr.design/products/components/table/#h3-2`
21
+
22
+ // buttonタグを使用している場合
23
+ const DO_NOT_USE_BUTTON_TAG = `BulkActionRow内では「Button」コンポーネントのみを使用してください。
24
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-bulk-action-row-button
25
+ - buttonタグは使用しないでください。
26
+ - もし「すべてのオブジェクトを選択」ボタンの実装であれば、Button[variant="tertiary"]を使用してください。
27
+ - 参考:
28
+ - https://smarthr.design/products/design-patterns/table-bulk-action/#h4-2
29
+ - https://smarthr.design/products/components/table/#h3-2`
30
+
31
+ /**
32
+ * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
33
+ */
34
+ module.exports = {
35
+ meta: {
36
+ type: 'problem',
37
+ schema: SCHEMA,
38
+ },
39
+ create(context) {
40
+ return {
41
+ // BulkActionRow内でaタグやLinkコンポーネントを使用している場合
42
+ 'JSXElement[openingElement.name.name=/BulkActionRow$/] JSXElement[openingElement.name.name=/^a$|Link$/]'(node) {
43
+ context.report({
44
+ node: node.openingElement,
45
+ message: DO_NOT_USE_LINK,
46
+ })
47
+ },
48
+
49
+ // BulkActionRow内でprefixが付いたButtonコンポーネントを使用している場合
50
+ 'JSXElement[openingElement.name.name=/BulkActionRow$/] JSXElement[openingElement.name.name=/.+Button$/]'(node) {
51
+ context.report({
52
+ node: node.openingElement,
53
+ message: DO_NOT_USE_PREFIXED_BUTTON,
54
+ })
55
+ },
56
+ // BulkActionRow内でbuttonタグを使用している場合
57
+ 'JSXElement[openingElement.name.name=/BulkActionRow$/] JSXElement[openingElement.name.name=/button$/]'(node) {
58
+ context.report({
59
+ node: node.openingElement,
60
+ message: DO_NOT_USE_BUTTON_TAG,
61
+ })
62
+ },
63
+ }
64
+ },
65
+ }
66
+
67
+ module.exports.schema = SCHEMA
@@ -0,0 +1,163 @@
1
+ # smarthr/design-system-guideline-prohibit-information-panel-in-white-bg
2
+
3
+ InformationPanelを白背景に配置することを禁止します。
4
+
5
+ ## なぜこのルールが必要か
6
+
7
+ SmartHR Design Systemのガイドラインに基づき、InformationPanelは白背景に直接配置すべきではありません。
8
+
9
+ **理由:**
10
+ - InformationPanelのレイヤー順序は3です
11
+ - Baseのレイヤー順序は1です
12
+ - 視覚的に適切な階層関係を保つため、白背景の上に直接配置することは避けるべきです
13
+
14
+ 詳細: https://smarthr.design/products/components/information-panel/#h3-1
15
+
16
+ ## 検出パターン
17
+
18
+ このルールは以下のパターンを検出します:
19
+
20
+ ### 1. Base内にInformationPanel
21
+
22
+ ```tsx
23
+ // ❌ Bad
24
+ <Base>
25
+ <InformationPanel>情報</InformationPanel>
26
+ </Base>
27
+
28
+ <Base>
29
+ <div>
30
+ <InformationPanel>情報</InformationPanel>
31
+ </div>
32
+ </Base>
33
+ ```
34
+
35
+ ### 2. Dialog内にInformationPanel(白背景)
36
+
37
+ contentBgColorが未指定、または"WHITE"が指定されている場合:
38
+
39
+ ```tsx
40
+ // ❌ Bad
41
+ <ActionDialog>
42
+ <InformationPanel>情報</InformationPanel>
43
+ </ActionDialog>
44
+
45
+ <FormDialog contentBgColor="WHITE">
46
+ <InformationPanel>情報</InformationPanel>
47
+ </FormDialog>
48
+ ```
49
+
50
+ ## 例外
51
+
52
+ 以下の場合はエラーになりません:
53
+
54
+ ### BaseColumn内にある場合
55
+
56
+ BaseColumnは背景色を持つため、その中にInformationPanelを配置することは問題ありません:
57
+
58
+ ```tsx
59
+ // ✅ Good
60
+ <Base>
61
+ <BaseColumn>
62
+ <InformationPanel>情報</InformationPanel>
63
+ </BaseColumn>
64
+ </Base>
65
+ ```
66
+
67
+ ### DialogでcontentBgColorが指定されている場合
68
+
69
+ ```tsx
70
+ // ✅ Good
71
+ <ActionDialog contentBgColor="COLUMN">
72
+ <InformationPanel>情報</InformationPanel>
73
+ </ActionDialog>
74
+
75
+ <FormDialog contentBgColor="OVER_BACKGROUND">
76
+ <InformationPanel>情報</InformationPanel>
77
+ </FormDialog>
78
+ ```
79
+
80
+ ## 正しい使用例
81
+
82
+ ```tsx
83
+ // ✅ Good: Stack/Clusterなどレイアウトコンポーネントで包む
84
+ <Stack>
85
+ <InformationPanel>情報</InformationPanel>
86
+ </Stack>
87
+
88
+ <Cluster>
89
+ <InformationPanel>情報</InformationPanel>
90
+ </Cluster>
91
+
92
+ // ✅ Good: BaseColumnを使用
93
+ <Base>
94
+ <BaseColumn>
95
+ <InformationPanel>情報</InformationPanel>
96
+ </BaseColumn>
97
+ </Base>
98
+
99
+ // ✅ Good: DialogでcontentBgColorを指定
100
+ <ActionDialog contentBgColor="COLUMN">
101
+ <InformationPanel>情報</InformationPanel>
102
+ </ActionDialog>
103
+
104
+ // ✅ Good: Base外で使用
105
+ <InformationPanel>情報</InformationPanel>
106
+ ```
107
+
108
+ ## 誤った使用例
109
+
110
+ ```tsx
111
+ // ❌ Bad: Base内に直接配置
112
+ <Base>
113
+ <InformationPanel>情報</InformationPanel>
114
+ </Base>
115
+
116
+ // ❌ Bad: Base内の他要素の中に配置
117
+ <Base>
118
+ <div>
119
+ <InformationPanel>情報</InformationPanel>
120
+ </div>
121
+ </Base>
122
+
123
+ // ❌ Bad: DialogでcontentBgColorが未指定
124
+ <ActionDialog>
125
+ <InformationPanel>情報</InformationPanel>
126
+ </ActionDialog>
127
+
128
+ // ❌ Bad: DialogでcontentBgColor="WHITE"
129
+ <FormDialog contentBgColor="WHITE">
130
+ <InformationPanel>情報</InformationPanel>
131
+ </FormDialog>
132
+ ```
133
+
134
+ ## よくある間違い
135
+
136
+ ### DialogContents内に配置
137
+
138
+ DialogContents(Dialog内部のコンテンツ領域)はデフォルトで白背景です。そのため、以下のようなコードはエラーになります:
139
+
140
+ ```tsx
141
+ // ❌ Bad
142
+ <ActionDialog>
143
+ <InformationPanel>情報</InformationPanel>
144
+ </ActionDialog>
145
+ ```
146
+
147
+ **解決方法:**
148
+ 1. contentBgColorを指定する(推奨)
149
+ 2. BaseColumnで包む
150
+
151
+ ```tsx
152
+ // ✅ Good: contentBgColorを指定
153
+ <ActionDialog contentBgColor="OVER_BACKGROUND">
154
+ <InformationPanel>情報</InformationPanel>
155
+ </ActionDialog>
156
+
157
+ // ✅ Good: BaseColumnで包む
158
+ <ActionDialog>
159
+ <BaseColumn>
160
+ <InformationPanel>情報</InformationPanel>
161
+ </BaseColumn>
162
+ </ActionDialog>
163
+ ```
@@ -0,0 +1,85 @@
1
+ /**
2
+ * InformationPanelを白背景に配置することを禁止するルール
3
+ *
4
+ * SmartHR Design System ガイドラインに基づき、InformationPanelを白背景に直接配置することを防ぎます。
5
+ *
6
+ * 検出パターン:
7
+ * 1. Base内にInformationPanel
8
+ * 2. Dialog内にInformationPanel(contentBgColorが未指定またはWHITE)
9
+ *
10
+ * 例外:
11
+ * - BaseColumn内にある場合(BaseColumnは背景色を持つため)
12
+ * - DialogでcontentBgColorがWHITE以外の場合
13
+ *
14
+ * @see https://smarthr.design/products/components/information-panel/
15
+ */
16
+
17
+ module.exports = {
18
+ meta: {
19
+ type: 'suggestion',
20
+ messages: {
21
+ inWhiteBg: 'InformationPanelを白背景に配置しないでください。InformationPanelをBaseColumnで包むか、InformationPanelを包んでいるDialogのcontentBgColorにWHITE以外の値を指定してください。詳細: https://smarthr.design/products/components/information-panel/',
22
+ },
23
+ schema: [],
24
+ },
25
+
26
+ create(context) {
27
+ /**
28
+ * InformationPanelから親方向に探索し、白背景コンテナをチェック
29
+ * @param {Node} node - InformationPanelのノード
30
+ * @param {string|RegExp} targetPattern - 検出対象のコンポーネント名パターン
31
+ * @returns {{ ok: boolean, targetNode?: Node }} 検出結果
32
+ */
33
+ const checkWhiteBg = (node, targetPattern) => {
34
+ let current = node.parent
35
+ while (current) {
36
+ if (current.type === 'JSXElement') {
37
+ const name = current.openingElement.name.name
38
+
39
+ // BaseColumnが見つかったらOK(探索終了)
40
+ if (name === 'BaseColumn') {
41
+ return { ok: true }
42
+ }
43
+
44
+ // targetパターンにマッチするか確認
45
+ const isTarget = typeof targetPattern === 'string'
46
+ ? name === targetPattern
47
+ : targetPattern.test(name)
48
+
49
+ if (isTarget) {
50
+ return { ok: false, targetNode: current }
51
+ }
52
+ }
53
+ current = current.parent
54
+ }
55
+ return { ok: true } // 何も見つからない場合はOK
56
+ }
57
+
58
+ return {
59
+ // Base以下のInformationPanel
60
+ 'JSXElement[openingElement.name.name="Base"] JSXElement[openingElement.name.name="InformationPanel"]'(node) {
61
+ const result = checkWhiteBg(node, 'Base')
62
+ if (!result.ok) {
63
+ context.report({ node, messageId: 'inWhiteBg' })
64
+ }
65
+ },
66
+
67
+ // Dialog以下のInformationPanel
68
+ 'JSXElement[openingElement.name.name=/Dialog$/] JSXElement[openingElement.name.name="InformationPanel"]'(node) {
69
+ const result = checkWhiteBg(node, /Dialog$/)
70
+ if (!result.ok) {
71
+ // contentBgColor属性をチェック
72
+ const attr = result.targetNode.openingElement.attributes.find(
73
+ a => a.type === 'JSXAttribute' && a.name.name === 'contentBgColor'
74
+ )
75
+ const bgColor = attr?.value?.value
76
+
77
+ // contentBgColorが未指定またはWHITEの場合のみエラー
78
+ if (!bgColor || bgColor === 'WHITE') {
79
+ context.report({ node, messageId: 'inWhiteBg' })
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,162 @@
1
+ const rule = require('../rules/design-system-guideline-bulk-action-row-button')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ languageOptions: {
6
+ parserOptions: {
7
+ ecmaFeatures: {
8
+ jsx: true,
9
+ },
10
+ },
11
+ },
12
+ })
13
+
14
+ const messageDoNotUseLink = `BulkActionRow内では「Button」コンポーネントを使用してください。
15
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-bulk-action-row-button
16
+ - BulkActionRowは一括操作用の領域であり、内部のクリッカブルな要素は画面遷移以外の動作を担います。
17
+ - a要素、AnchorButton、Linkは画面遷移を示すセマンティックなマークアップのため、BulkActionRow内では不適切です。
18
+ - もし「すべてのオブジェクトを選択」ボタンの実装であれば、Button[variant="tertiary"]を使用してください。
19
+ - 参考:
20
+ - https://smarthr.design/products/design-patterns/table-bulk-action/#h4-2
21
+ - https://smarthr.design/products/components/table/#h3-2`
22
+
23
+ const messageDoNotUsePrefixedButton = `BulkActionRow内では「Button」コンポーネントのみを使用してください。
24
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-bulk-action-row-button
25
+ - XxxxButtonのようにprefixが付いたButtonコンポーネント(AnchorButton、StyledButtonなど)は使用しないでください。
26
+ - もし「すべてのオブジェクトを選択」ボタンの実装であれば、Button[variant="tertiary"]を使用してください。
27
+ - 参考:
28
+ - https://smarthr.design/products/design-patterns/table-bulk-action/#h4-2
29
+ - https://smarthr.design/products/components/table/#h3-2`
30
+
31
+ const messageDoNotUseButtonTag = `BulkActionRow内では「Button」コンポーネントのみを使用してください。
32
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-bulk-action-row-button
33
+ - buttonタグは使用しないでください。
34
+ - もし「すべてのオブジェクトを選択」ボタンの実装であれば、Button[variant="tertiary"]を使用してください。
35
+ - 参考:
36
+ - https://smarthr.design/products/design-patterns/table-bulk-action/#h4-2
37
+ - https://smarthr.design/products/components/table/#h3-2`
38
+
39
+ ruleTester.run('design-system-guideline-bulk-action-row-button', rule, {
40
+ valid: [
41
+ // BulkActionRow内でButtonを使用
42
+ {
43
+ code: `
44
+ <BulkActionRow>
45
+ <Button variant="primary">一括削除</Button>
46
+ </BulkActionRow>
47
+ `,
48
+ },
49
+ //「すべてのオブジェクトの選択」ボタンにvariant="tertiary"を指定
50
+ {
51
+ code: `
52
+ <Table>
53
+ <thead>
54
+ <BulkActionRow>
55
+ <Cluster align="center">
56
+ <Text>このページの「オブジェクト名」50件すべて選択されています。</Text>
57
+ <Button variant="tertiary" size="S">
58
+ 一覧の「オブジェクト名」1000件すべてを選択
59
+ </Button>
60
+ </Cluster>
61
+ </BulkActionRow>
62
+ </thead>
63
+ </Table>
64
+ `,
65
+ },
66
+ // ButtonClusterなど、Buttonで終わらないコンポーネントは誤検知しない
67
+ {
68
+ code: `
69
+ <BulkActionRow>
70
+ <ButtonCluster>
71
+ <Button>削除</Button>
72
+ <Button>編集</Button>
73
+ </ButtonCluster>
74
+ </BulkActionRow>
75
+ `,
76
+ },
77
+ ],
78
+ invalid: [
79
+ // BulkActionRow内でaタグを使用
80
+ {
81
+ code: `
82
+ <BulkActionRow>
83
+ <a href="#" onClick={toggleAll}>一覧の100件すべてを選択</a>
84
+ </BulkActionRow>
85
+ `,
86
+ errors: [{ message: messageDoNotUseLink }],
87
+ },
88
+ // BulkActionRow内でTextLinkを使用
89
+ {
90
+ code: `
91
+ <BulkActionRow>
92
+ <TextLink href={undefined} onClick={toggleAll}>
93
+ 一覧の100件すべてを選択
94
+ </TextLink>
95
+ </BulkActionRow>
96
+ `,
97
+ errors: [{ message: messageDoNotUseLink }],
98
+ },
99
+ // BulkActionRow内でprefixが付いたButtonコンポーネントを使用
100
+ {
101
+ code: `
102
+ <BulkActionRow>
103
+ <StyledSelectAllButton>一覧のすべてを選択</StyledSelectAllButton>
104
+ </BulkActionRow>
105
+ `,
106
+ errors: [{ message: messageDoNotUsePrefixedButton }],
107
+ },
108
+ // AnchorButtonを使用
109
+ {
110
+ code: `
111
+ <BulkActionRow>
112
+ <AnchorButton href={undefined} onClick={toggleAllChecked}>
113
+ 一覧のすべてを選択
114
+ </AnchorButton>
115
+ </BulkActionRow>
116
+ `,
117
+ errors: [{ message: messageDoNotUsePrefixedButton }],
118
+ },
119
+ // ネストされた構造でもチェック
120
+ {
121
+ code: `
122
+ <BulkActionRow>
123
+ <Cluster>
124
+ <Stack>
125
+ <CustomButton>操作</CustomButton>
126
+ </Stack>
127
+ </Cluster>
128
+ </BulkActionRow>
129
+ `,
130
+ errors: [{ message: messageDoNotUsePrefixedButton }],
131
+ },
132
+ // StyledBulkActionRowのようなラッパーコンポーネント内でもチェック
133
+ {
134
+ code: `
135
+ <StyledBulkActionRow>
136
+ <TextLink href={undefined} onClick={toggleAll}>
137
+ 一覧の100件すべてを選択
138
+ </TextLink>
139
+ </StyledBulkActionRow>
140
+ `,
141
+ errors: [{ message: messageDoNotUseLink }],
142
+ },
143
+ {
144
+ code: `
145
+ <CustomBulkActionRow>
146
+ <AnchorButton href={undefined} onClick={toggleAllChecked}>
147
+ 一覧のすべてを選択
148
+ </AnchorButton>
149
+ </CustomBulkActionRow>
150
+ `,
151
+ errors: [{ message: messageDoNotUsePrefixedButton }],
152
+ },
153
+ {
154
+ code: `
155
+ <BulkActionRow>
156
+ <button>一括削除</button>
157
+ </BulkActionRow>
158
+ `,
159
+ errors: [{ message: messageDoNotUseButtonTag }],
160
+ },
161
+ ],
162
+ })
@@ -0,0 +1,128 @@
1
+ const rule = require('../rules/design-system-guideline-prohibit-information-panel-in-white-bg')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ languageOptions: {
6
+ parserOptions: {
7
+ ecmaFeatures: {
8
+ jsx: true,
9
+ },
10
+ },
11
+ },
12
+ })
13
+
14
+ ruleTester.run('design-system-guideline-prohibit-information-panel-in-white-bg', rule, {
15
+ valid: [
16
+ // InformationPanel単体(Base外)
17
+ {
18
+ code: '<InformationPanel>情報</InformationPanel>',
19
+ },
20
+
21
+ // Base > BaseColumn > InformationPanel
22
+ {
23
+ code: '<Base><BaseColumn><InformationPanel>情報</InformationPanel></BaseColumn></Base>',
24
+ },
25
+
26
+ // Stack内
27
+ {
28
+ code: '<Stack><InformationPanel>情報</InformationPanel></Stack>',
29
+ },
30
+
31
+ // Cluster内
32
+ {
33
+ code: '<Cluster><InformationPanel>情報</InformationPanel></Cluster>',
34
+ },
35
+
36
+ // Base > BaseColumn > div > InformationPanel
37
+ {
38
+ code: '<Base><BaseColumn><div><InformationPanel>情報</InformationPanel></div></BaseColumn></Base>',
39
+ },
40
+
41
+ // ActionDialog with contentBgColor="COLUMN"
42
+ {
43
+ code: '<ActionDialog contentBgColor="COLUMN"><InformationPanel>情報</InformationPanel></ActionDialog>',
44
+ },
45
+
46
+ // FormDialog with contentBgColor="OVER_BACKGROUND"
47
+ {
48
+ code: '<FormDialog contentBgColor="OVER_BACKGROUND"><InformationPanel>情報</InformationPanel></FormDialog>',
49
+ },
50
+
51
+ // MessageDialog with contentBgColor="COLUMN"
52
+ {
53
+ code: '<MessageDialog contentBgColor="COLUMN"><InformationPanel>情報</InformationPanel></MessageDialog>',
54
+ },
55
+
56
+ // StepFormDialog with contentBgColor="OVER_BACKGROUND"
57
+ {
58
+ code: '<StepFormDialog contentBgColor="OVER_BACKGROUND"><InformationPanel>情報</InformationPanel></StepFormDialog>',
59
+ },
60
+
61
+ // Dialog > BaseColumn > InformationPanel
62
+ {
63
+ code: '<ActionDialog><BaseColumn><InformationPanel>情報</InformationPanel></BaseColumn></ActionDialog>',
64
+ },
65
+ ],
66
+
67
+ invalid: [
68
+ // Base直下
69
+ {
70
+ code: '<Base><InformationPanel>情報</InformationPanel></Base>',
71
+ errors: [{ messageId: 'inWhiteBg' }],
72
+ },
73
+
74
+ // Base > div > InformationPanel
75
+ {
76
+ code: '<Base><div><InformationPanel>情報</InformationPanel></div></Base>',
77
+ errors: [{ messageId: 'inWhiteBg' }],
78
+ },
79
+
80
+ // Base > Stack > InformationPanel
81
+ {
82
+ code: '<Base><Stack><InformationPanel>情報</InformationPanel></Stack></Base>',
83
+ errors: [{ messageId: 'inWhiteBg' }],
84
+ },
85
+
86
+ // Base > BaseColumn > Base > InformationPanel
87
+ {
88
+ code: '<Base><BaseColumn><Base><InformationPanel>情報</InformationPanel></Base></BaseColumn></Base>',
89
+ errors: [{ messageId: 'inWhiteBg' }],
90
+ },
91
+
92
+ // ActionDialog(contentBgColor未指定)
93
+ {
94
+ code: '<ActionDialog><InformationPanel>情報</InformationPanel></ActionDialog>',
95
+ errors: [{ messageId: 'inWhiteBg' }],
96
+ },
97
+
98
+ // FormDialog(contentBgColor="WHITE")
99
+ {
100
+ code: '<FormDialog contentBgColor="WHITE"><InformationPanel>情報</InformationPanel></FormDialog>',
101
+ errors: [{ messageId: 'inWhiteBg' }],
102
+ },
103
+
104
+ // MessageDialog(contentBgColor未指定)
105
+ {
106
+ code: '<MessageDialog><InformationPanel>情報</InformationPanel></MessageDialog>',
107
+ errors: [{ messageId: 'inWhiteBg' }],
108
+ },
109
+
110
+ // StepFormDialog(contentBgColor="WHITE")
111
+ {
112
+ code: '<StepFormDialog contentBgColor="WHITE"><InformationPanel>情報</InformationPanel></StepFormDialog>',
113
+ errors: [{ messageId: 'inWhiteBg' }],
114
+ },
115
+
116
+ // ActionDialog > div > InformationPanel(contentBgColor未指定)
117
+ {
118
+ code: '<ActionDialog><div><InformationPanel>情報</InformationPanel></div></ActionDialog>',
119
+ errors: [{ messageId: 'inWhiteBg' }],
120
+ },
121
+
122
+ // ネスト: Base > div > Base > InformationPanel
123
+ {
124
+ code: '<Base><div><Base><InformationPanel>情報</InformationPanel></Base></div></Base>',
125
+ errors: [{ messageId: 'inWhiteBg' }],
126
+ },
127
+ ],
128
+ })