@junyiacademy/ui-test 1.4.0 → 1.4.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.
Files changed (33) hide show
  1. package/declarations/libs/ui/src/interfaces/index.d.ts +1 -2
  2. package/declarations/libs/ui/src/lib/menu-item/SelectMenuItem.d.ts +1 -2
  3. package/declarations/libs/ui/src/lib/select/OutlinedSelect.d.ts +1 -1
  4. package/declarations/libs/ui/src/lib/select/StandardSelect.d.ts +1 -1
  5. package/dist/libs/ui/src/lib/menu-item/SelectMenuItem.js +4 -8
  6. package/dist/libs/ui/src/lib/select/OutlinedSelect.js +4 -6
  7. package/dist/libs/ui/src/lib/select/StandardSelect.js +5 -12
  8. package/dist/libs/ui/src/lib/topic-filter/TopicFilter.js +8 -5
  9. package/package.json +2 -2
  10. package/.storybook/main.js +0 -9
  11. package/.storybook/preview.js +0 -10
  12. package/.storybook/tsconfig.json +0 -14
  13. package/.storybook/webpack.config.js +0 -84
  14. package/src/index.ts +0 -7
  15. package/src/interfaces/index.tsx +0 -34
  16. package/src/lib/button/Button.stories.tsx +0 -50
  17. package/src/lib/button/Button.tsx +0 -85
  18. package/src/lib/button-group/ButtonGroup.stories.tsx +0 -59
  19. package/src/lib/button-group/ButtonGroup.tsx +0 -37
  20. package/src/lib/menu-item/SelectMenuItem.stories.tsx +0 -44
  21. package/src/lib/menu-item/SelectMenuItem.tsx +0 -48
  22. package/src/lib/radio/Radio.stories.tsx +0 -154
  23. package/src/lib/radio/Radio.tsx +0 -93
  24. package/src/lib/select/OutlinedSelect.tsx +0 -220
  25. package/src/lib/select/Select.stories.tsx +0 -306
  26. package/src/lib/select/Select.tsx +0 -13
  27. package/src/lib/select/StandardSelect.tsx +0 -178
  28. package/src/lib/text-field/TextField.stories.tsx +0 -160
  29. package/src/lib/text-field/TextField.tsx +0 -93
  30. package/src/lib/topic-filter/TopicFilter.stories.tsx +0 -83
  31. package/src/lib/topic-filter/TopicFilter.tsx +0 -209
  32. package/src/styles/theme.ts +0 -60
  33. package/src/utils/topicTree.ts +0 -197
@@ -1,93 +0,0 @@
1
- import React from 'react'
2
- import { styled } from '@material-ui/core/styles'
3
- import { TextField as MuiTextField, TextFieldProps } from '@material-ui/core'
4
-
5
- // self-defined-components
6
- const PREFIX = 'JuiTextField'
7
-
8
- const classes = {
9
- inputLabelError: `${PREFIX}-inputLabel-error`,
10
- inputLabelDisabled: `${PREFIX}-inputLabel-disabled`,
11
- inputLabelShrink: `${PREFIX}-inputLabel-shrink`,
12
- inputLabelFocused: `${PREFIX}-inputLabel-focused`,
13
- inputNotchedOutline: `${PREFIX}-input-notchedOutline`,
14
- inputUnderline: `${PREFIX}-input-underline`,
15
- inputColorSecondary: `${PREFIX}-input-colorSecondary`,
16
- inputDisabled: `${PREFIX}-input-disabled`,
17
- inputError: `${PREFIX}-input-error`,
18
- inputFocused: `${PREFIX}-input-focused`,
19
- }
20
-
21
- const StyledTextField = styled(
22
- ({ hasEndAdornment, InputLabelProps, InputProps, ...otherProps }) => (
23
- <MuiTextField
24
- InputLabelProps={{
25
- ...InputLabelProps,
26
- classes: {
27
- error: classes.inputLabelError,
28
- disabled: classes.inputLabelDisabled,
29
- shrink: classes.inputLabelShrink,
30
- focused: classes.inputLabelFocused,
31
- },
32
- shrink: hasEndAdornment ? true : undefined,
33
- }}
34
- InputProps={{
35
- ...InputProps,
36
- classes: {
37
- colorSecondary: classes.inputColorSecondary,
38
- disabled: classes.inputDisabled,
39
- error: classes.inputError,
40
- focused: classes.inputFocused,
41
- ...(otherProps?.variant === 'outlined'
42
- ? { notchedOutline: classes.inputNotchedOutline }
43
- : { underline: classes.inputUnderline }),
44
- },
45
- }}
46
- {...otherProps}
47
- />
48
- )
49
- )(({ theme }) => ({
50
- [`& .${classes.inputLabelError}`]: {
51
- [`&:not(.${classes.inputLabelShrink})`]: {
52
- color: theme.palette.text.secondary,
53
- },
54
- [`&.${classes.inputLabelDisabled}`]: {
55
- color: theme.palette.text.disabled,
56
- },
57
- },
58
- [`& .${classes.inputLabelError}.${classes.inputLabelFocused}`]: {
59
- color: theme.palette.error.main,
60
- },
61
- // For variant: standard | filled
62
- [`& .${classes.inputUnderline}:not(.${classes.inputDisabled})`]: {
63
- [`&:hover:before`]: {
64
- borderColor: theme.palette.primary.main,
65
- },
66
- [`&.${classes.inputColorSecondary}:hover:before`]: {
67
- borderColor: theme.palette.secondary.main,
68
- },
69
- },
70
- // For variant: outlined
71
- [`& .${classes.inputDisabled} .${classes.inputNotchedOutline}`]: {
72
- borderStyle: 'dotted',
73
- },
74
- [`& .${classes.inputError}.${classes.inputFocused} .${classes.inputNotchedOutline}`]: {
75
- borderColor: theme.palette.error.main,
76
- },
77
- [`& :not(.${classes.inputDisabled}):not(.${classes.inputError})`]: {
78
- [`&:hover .${classes.inputNotchedOutline}`]: {
79
- borderColor: theme.palette.primary.main,
80
- },
81
- [`&.${classes.inputColorSecondary}:hover .${classes.inputNotchedOutline}`]: {
82
- borderColor: theme.palette.secondary.main,
83
- },
84
- },
85
- }))
86
-
87
- export const TextField = (props: TextFieldProps) => {
88
- const hasEndAdornment = !!props?.InputProps?.endAdornment
89
-
90
- return <StyledTextField hasEndAdornment={hasEndAdornment} {...props} />
91
- }
92
-
93
- export default TextField
@@ -1,83 +0,0 @@
1
- import React from 'react'
2
- import { Story, Meta } from '@storybook/react'
3
- import {
4
- TopicFilter as JuiTopicFilter,
5
- TopicFilterProps as JuiTopicFilterProps,
6
- } from './TopicFilter'
7
- import { topicTree } from '../../utils/topicTree'
8
-
9
- export default {
10
- component: JuiTopicFilter,
11
- title: 'TopicFilter',
12
- argTypes: {
13
- topicTree: {
14
- type: { name: 'object', required: false },
15
- description: 'Topic tree data list',
16
- table: {
17
- type: { summary: 'object' },
18
- defaultValue: { summary: topicTree },
19
- },
20
- control: { type: 'object' },
21
- },
22
- onTopicSelected: {
23
- type: { name: 'function', required: false },
24
- description: 'Callback fired when the topic selected.',
25
- table: {
26
- type: {
27
- summary: `(
28
- topic: Topic,
29
- selectedInfo: { layerNumber: number; selectedTopicIds: string[] }
30
- ) => void`,
31
- },
32
- defaultValue: { summary: () => {} },
33
- },
34
- control: { type: 'function' },
35
- },
36
- isLastLayer: {
37
- type: { name: 'function', required: false },
38
- description: 'Callback for checking selected topic is last layer or not.',
39
- table: {
40
- type: {
41
- summary: `(topic: Topic) => boolean`,
42
- },
43
- defaultValue: { summary: () => false },
44
- },
45
- control: { type: 'function' },
46
- },
47
- hasArrow: {
48
- type: { name: 'boolean', required: false },
49
- description: 'Control the arrow display state.',
50
- table: {
51
- type: { summary: 'boolean' },
52
- defaultValue: { summary: true },
53
- },
54
- control: { type: 'boolean' },
55
- defaultValue: true,
56
- },
57
- initSelectedTopicIds: {
58
- type: { name: 'array', required: false },
59
- description: ``,
60
- table: {
61
- type: { summary: 'array' },
62
- defaultValue: { summary: [] },
63
- },
64
- control: { type: 'array' },
65
- },
66
- },
67
- } as Meta
68
-
69
- const TopicFilterStory: Story<JuiTopicFilterProps> = (args) => (
70
- <JuiTopicFilter {...args} />
71
- )
72
-
73
- export const TopicFilter = TopicFilterStory.bind({})
74
-
75
- TopicFilter.args = {
76
- topicTree: topicTree,
77
- onTopicSelected: () => {},
78
- isLastLayer: (selectedTopic) => {
79
- return selectedTopic.isContentTopic
80
- },
81
- hasArrow: true,
82
- initSelectedTopicIds: [],
83
- }
@@ -1,209 +0,0 @@
1
- import React, { useState, useEffect } from 'react'
2
- import { Theme, styled, useTheme } from '@material-ui/core/styles'
3
- import ArrowRightRoundedIcon from '@material-ui/icons/ArrowRightRounded'
4
- import type { ITopicTreeNode } from '../../interfaces'
5
- import Select from '../select/Select'
6
- import SelectMenuItem from '../menu-item/SelectMenuItem'
7
-
8
- // self-defined-configs
9
- const PLACEHOLDER = '請選擇'
10
-
11
- // self-defined-components
12
- const PREFIX = 'JuiTopicFilter'
13
-
14
- const FiltersWrapper = styled('div')({
15
- display: 'flex',
16
- alignItems: 'center',
17
- flexWrap: 'wrap',
18
- })
19
-
20
- const SelectWrapper = styled('div')({
21
- display: 'flex',
22
- alignItems: 'center',
23
- })
24
-
25
- interface StyledArrowRightRoundedIconProps {
26
- theme?: Theme
27
- }
28
-
29
- const StyledArrowRightRoundedIcon = styled(ArrowRightRoundedIcon)(
30
- ({ theme }: StyledArrowRightRoundedIconProps) => ({
31
- margin: theme.spacing(-1, -1.5),
32
- fontSize: theme.spacing(7),
33
- color: '#444',
34
- })
35
- )
36
-
37
- export interface TopicFilterProps {
38
- topicTree: ITopicTreeNode
39
- onTopicSelected: (
40
- topic: ITopicTreeNode,
41
- selectedInfo: { layerNumber: number; selectedTopicIds: string[] }
42
- ) => void
43
- isLastLayer: (topic: ITopicTreeNode) => boolean
44
- hasArrow: boolean
45
- initSelectedTopicIds: string[]
46
- }
47
-
48
- export const TopicFilter = ({
49
- topicTree,
50
- onTopicSelected,
51
- isLastLayer,
52
- hasArrow,
53
- initSelectedTopicIds,
54
- }: TopicFilterProps) => {
55
- const theme = useTheme()
56
- const [selectedTopicIds, setSelectedTopicIds] = useState([])
57
- const [layeredTopicList, setLayeredTopicList] = useState([])
58
- const [isFocusedList, setIsFocusedList] = useState([])
59
- const initSelectedLayers = () => {
60
- const newLayeredTopicList = initSelectedTopicIds.reduce(
61
- (topicListAccumulator, topicId, index) => {
62
- const selectedTopic = topicListAccumulator[index]?.childTopics?.find(
63
- (childTopic) => childTopic.id === topicId
64
- )
65
-
66
- if (!selectedTopic) {
67
- return topicListAccumulator
68
- }
69
- if (isLastLayer(selectedTopic)) {
70
- return topicListAccumulator
71
- }
72
- return [...topicListAccumulator, selectedTopic]
73
- },
74
- [topicTree]
75
- )
76
-
77
- setLayeredTopicList(newLayeredTopicList)
78
- setSelectedTopicIds(
79
- initSelectedTopicIds.slice(0, newLayeredTopicList.length)
80
- )
81
- setIsFocusedList(Array(newLayeredTopicList.length).fill(false))
82
- }
83
-
84
- const handleChange = (e, layerNumber, layeredTopic) => {
85
- const selectedTopic = layeredTopic.childTopics.find(
86
- (childTopic) => childTopic.id === e.target.value
87
- )
88
-
89
- const newSelectedTopicIds = [
90
- ...selectedTopicIds.slice(0, layerNumber),
91
- selectedTopic.id,
92
- ]
93
- setSelectedTopicIds(newSelectedTopicIds)
94
- onTopicSelected(selectedTopic, {
95
- layerNumber,
96
- selectedTopicIds: newSelectedTopicIds,
97
- })
98
-
99
- if (isLastLayer(selectedTopic)) {
100
- setLayeredTopicList((prevTopicList) =>
101
- prevTopicList.slice(0, layerNumber + 1)
102
- )
103
- setIsFocusedList((prevList) => prevList.slice(0, layerNumber + 1))
104
- } else {
105
- setLayeredTopicList((prevTopicList) => [
106
- ...prevTopicList.slice(0, layerNumber + 1),
107
- selectedTopic,
108
- ])
109
- setIsFocusedList((prevList) => [
110
- ...prevList.slice(0, layerNumber + 1),
111
- false,
112
- ])
113
- }
114
- }
115
-
116
- useEffect(() => {
117
- if (!topicTree || Object.keys(topicTree).length === 0) {
118
- return
119
- }
120
- if (initSelectedTopicIds.length !== 0) {
121
- initSelectedLayers()
122
- return
123
- }
124
- setLayeredTopicList([topicTree])
125
- }, [topicTree])
126
-
127
- if (layeredTopicList.length === 0) {
128
- return (
129
- <Select
130
- variant='outlined'
131
- size='small'
132
- width={220}
133
- selectMargin={theme.spacing(1)}
134
- placeholder='載入資料中...'
135
- disabled
136
- ></Select>
137
- )
138
- }
139
- return (
140
- <FiltersWrapper>
141
- {layeredTopicList.map((layeredTopic, layerNumber) => {
142
- const hasLabel =
143
- isFocusedList[layerNumber] || !selectedTopicIds[layerNumber]
144
- return (
145
- <SelectWrapper key={layeredTopic.id}>
146
- <Select
147
- variant='outlined'
148
- size='small'
149
- width={220}
150
- selectMargin={theme.spacing(1)}
151
- paperMaxHeight={412}
152
- hasLabel={hasLabel}
153
- placeholder={PLACEHOLDER}
154
- value={selectedTopicIds?.[layerNumber] || ''}
155
- SelectProps={{
156
- 'data-testid': `layered-topic-${layerNumber}`,
157
- }}
158
- InputProps={{
159
- inputProps: {
160
- 'aria-label': `layered-topic-${layerNumber}`,
161
- },
162
- onChange: (e) => {
163
- handleChange(e, layerNumber, layeredTopic)
164
- },
165
- onFocus: () => {
166
- setIsFocusedList((prevList) => {
167
- const newList = [...prevList]
168
- newList[layerNumber] = true
169
- return newList
170
- })
171
- },
172
- onBlur: () => {
173
- setIsFocusedList((prevList) => {
174
- const newList = [...prevList]
175
- newList[layerNumber] = false
176
- return newList
177
- })
178
- },
179
- }}
180
- >
181
- <SelectMenuItem width={220} disabled>
182
- {PLACEHOLDER}
183
- </SelectMenuItem>
184
- {layeredTopic.childTopics.map((childTopic) => (
185
- <SelectMenuItem
186
- width={220}
187
- key={childTopic.id}
188
- value={childTopic.id}
189
- data-testid={`layered-menuitem-${layerNumber}`}
190
- data-is-content-topic={childTopic.isContentTopic}
191
- >
192
- {childTopic.title}
193
- </SelectMenuItem>
194
- ))}
195
- </Select>
196
- {hasArrow && layerNumber !== layeredTopicList.length - 1 && (
197
- <StyledArrowRightRoundedIcon
198
- fontSize='large'
199
- data-testid='topic-filter-arrow'
200
- ></StyledArrowRightRoundedIcon>
201
- )}
202
- </SelectWrapper>
203
- )
204
- })}
205
- </FiltersWrapper>
206
- )
207
- }
208
-
209
- export default TopicFilter
@@ -1,60 +0,0 @@
1
- /**
2
- * Copyright (c) 2020 Junyi Academy.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
- import { createMuiTheme } from '@material-ui/core/styles'
9
-
10
- declare module '@material-ui/core/styles/createPalette' {
11
- interface Palette {
12
- green: {
13
- primary: Palette['primary']
14
- blueGreen: Palette['primary']
15
- grassGreen: Palette['primary']
16
- }
17
- }
18
- interface PaletteOptions {
19
- green: {
20
- primary: PaletteOptions['primary']
21
- blueGreen: PaletteOptions['primary']
22
- grassGreen: PaletteOptions['primary']
23
- }
24
- }
25
- }
26
-
27
- // Create a theme instance.
28
- const theme = createMuiTheme({
29
- typography: {
30
- fontFamily: ['Noto Sans TC', 'Helvetica', 'Arial', 'sans-serif'].join(','),
31
- },
32
- palette: {
33
- primary: {
34
- light: '#82C0FF',
35
- main: '#4990E2',
36
- dark: '#0063B0',
37
- },
38
- secondary: {
39
- light: '#FFD759',
40
- main: '#F5A623',
41
- dark: '#BD7700',
42
- contrastText: '#FFFFFF',
43
- },
44
- green: {
45
- primary: {
46
- light: '#9DD49E',
47
- main: '#5CB85D',
48
- dark: '#218838',
49
- },
50
- blueGreen: {
51
- main: '#19A696',
52
- },
53
- grassGreen: {
54
- main: '#80BB5A',
55
- },
56
- },
57
- },
58
- })
59
-
60
- export default theme
@@ -1,197 +0,0 @@
1
- import { ITopicTreeNode } from '../interfaces'
2
-
3
- export const topicTree: ITopicTreeNode = {
4
- childTopics: [
5
- {
6
- childTopics: [
7
- {
8
- childTopics: [
9
- {
10
- childTopics: [
11
- {
12
- childTopics: [],
13
- id: 'j-m1ach1',
14
- isContentTopic: true,
15
- key:
16
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKFlWUkdyck1reDdXU3lSb3pQYXU4RDlDTnZ4Ukx3MjhYU2ZCcTF0LTEM',
17
- tags: [],
18
- title: '【一上】數到 10',
19
- },
20
- {
21
- childTopics: [],
22
- id: 'j-m1ach2',
23
- isContentTopic: true,
24
- key:
25
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
26
- tags: [],
27
- title: '【一上】比長短1',
28
- },
29
- {
30
- childTopics: [],
31
- id: 'j-m1ach3',
32
- isContentTopic: true,
33
- key:
34
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
35
- tags: [],
36
- title: '【一上】比長短2',
37
- },
38
- {
39
- childTopics: [],
40
- id: 'j-m1ach4',
41
- isContentTopic: true,
42
- key:
43
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
44
- tags: [],
45
- title: '【一上】比長短3',
46
- },
47
- {
48
- childTopics: [],
49
- id: 'j-m1ach5',
50
- isContentTopic: true,
51
- key:
52
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
53
- tags: [],
54
- title: '【一上】比長短4',
55
- },
56
- {
57
- childTopics: [],
58
- id: 'j-m1ach6',
59
- isContentTopic: true,
60
- key:
61
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
62
- tags: [],
63
- title: '【一上】比長短5',
64
- },
65
- {
66
- childTopics: [],
67
- id: 'j-m1ach7',
68
- isContentTopic: true,
69
- key:
70
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
71
- tags: [],
72
- title: '【一上】比長短6',
73
- },
74
- {
75
- childTopics: [],
76
- id: 'j-m1ach8',
77
- isContentTopic: true,
78
- key:
79
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
80
- tags: [],
81
- title: '【一上】比長短7',
82
- },
83
- {
84
- childTopics: [],
85
- id: 'j-m1ach9',
86
- isContentTopic: true,
87
- key:
88
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
89
- tags: [],
90
- title: '【一上】比長短8',
91
- },
92
- {
93
- childTopics: [],
94
- id: 'j-m1ach10',
95
- isContentTopic: true,
96
- key:
97
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
98
- tags: [],
99
- title: '【一上】比長短9',
100
- },
101
- {
102
- childTopics: [],
103
- id: 'j-m1ach11',
104
- isContentTopic: true,
105
- key:
106
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
107
- tags: [],
108
- title: '【一上】比長短10',
109
- },
110
- {
111
- childTopics: [],
112
- id: 'j-m1ach12',
113
- isContentTopic: true,
114
- key:
115
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHRXZlVrTHNpSjRYcHlMTGFDa2QtVTRDWkRjcjMwZ3JSTGtSUUNNazgM',
116
- tags: [],
117
- title: '【一上】比長短11',
118
- },
119
- ],
120
- id: 'math-grade-1-a',
121
- isContentTopic: false,
122
- key:
123
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHN2MlIzS0VwaXlVeWdJQnhyYzBYakZ0RDZhU0JraGFJbGctU19qZFoM',
124
- tags: [],
125
- title: '均一版',
126
- },
127
- ],
128
- id: 'math-1',
129
- isContentTopic: false,
130
- key:
131
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKEVzRnVINFdtalZBRmdQUkpjeWtaWFVnSlR2OEc2RzVsWnA1a0ZEVV8M',
132
- tags: ['grouping_eleme'],
133
- title: '一年級',
134
- },
135
- {
136
- childTopics: [
137
- {
138
- childTopics: [
139
- {
140
- childTopics: [],
141
- id: 'n-m2ach1',
142
- isContentTopic: true,
143
- key:
144
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKExzUnEyWUgzUEduZmVzWm1oVXdxLU5tdmxjZmx4aWJXbkVKN0JVcXYM',
145
- tags: [],
146
- title: '【二上】第一單元 數到 300',
147
- },
148
- {
149
- childTopics: [],
150
- id: 'n-m2ach2',
151
- isContentTopic: true,
152
- key:
153
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKFJuTDZLWXhxS0xNQ3VPU3BkUXZBOVN6YkRrVTNqdVNRSFdjbTEwc2UM',
154
- tags: [],
155
- title: '【二上】第二單元 二位數的加法',
156
- },
157
- {
158
- childTopics: [],
159
- id: 'n-m2ach3',
160
- isContentTopic: true,
161
- key:
162
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHpYUWFzZXNPbS16V1BVeG9qeVFRQXlfZzA5Wm44ajdVZnM1dU85UWkM',
163
- tags: [],
164
- title: '【二上】第三單元 幾公分',
165
- },
166
- ],
167
- id: 'n-m2a',
168
- isContentTopic: false,
169
- key:
170
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKHl3QXRqNW1xMmk2QTRfR0JzWlpXQnVRQW1fYW5ZTEV4MkJ2ZUlYSmUM',
171
- tags: [],
172
- title: '南一版',
173
- },
174
- ],
175
- id: 'math-2',
176
- isContentTopic: false,
177
- key:
178
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKE92cm9EU1N4c3FZM2J6MzMzZ05XWFR6RGFLMDJudHlIaHFtN1VRcXIM',
179
- tags: ['grouping_eleme'],
180
- title: '二年級',
181
- },
182
- ],
183
- id: 'course-compare',
184
- isContentTopic: false,
185
- key:
186
- 'ag5zfmp1bnlpYWNhZGVteXJmCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoMCxIFVG9waWMiKDFCSmFuVjVsTWlUUWZaU21MeE9FU3BZbVlmcktkNHJWN3Q5WU1VWjQM',
187
- tags: ['has-pre-exam', 'has-post-exam'],
188
- title: '數學',
189
- },
190
- ],
191
- id: 'root',
192
- isContentTopic: false,
193
- key:
194
- 'ag5zfmp1bnlpYWNhZGVteXIzCxIFVG9waWMiKHpWQ01BOGpXQTU5SW1zSi1sdWJSQTJlV0QzbHpuZUZiY1BwMUZWOVoM',
195
- tags: [],
196
- title: '知識樹',
197
- }