mtrl 0.0.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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/index.js +10 -0
  4. package/package.json +17 -0
  5. package/src/components/button/api.js +54 -0
  6. package/src/components/button/button.js +81 -0
  7. package/src/components/button/config.js +8 -0
  8. package/src/components/button/constants.js +63 -0
  9. package/src/components/button/index.js +2 -0
  10. package/src/components/button/styles.scss +231 -0
  11. package/src/components/checkbox/api.js +45 -0
  12. package/src/components/checkbox/checkbox.js +95 -0
  13. package/src/components/checkbox/constants.js +88 -0
  14. package/src/components/checkbox/index.js +2 -0
  15. package/src/components/checkbox/styles.scss +183 -0
  16. package/src/components/container/api.js +42 -0
  17. package/src/components/container/container.js +45 -0
  18. package/src/components/container/index.js +2 -0
  19. package/src/components/container/styles.scss +59 -0
  20. package/src/components/list/constants.js +89 -0
  21. package/src/components/list/index.js +2 -0
  22. package/src/components/list/list-item.js +147 -0
  23. package/src/components/list/list.js +267 -0
  24. package/src/components/list/styles/_list-item.scss +142 -0
  25. package/src/components/list/styles/_list.scss +89 -0
  26. package/src/components/list/styles/_variables.scss +13 -0
  27. package/src/components/list/styles.scss +19 -0
  28. package/src/components/navigation/api.js +43 -0
  29. package/src/components/navigation/constants.js +235 -0
  30. package/src/components/navigation/features/items.js +192 -0
  31. package/src/components/navigation/index.js +2 -0
  32. package/src/components/navigation/nav-item.js +137 -0
  33. package/src/components/navigation/navigation.js +55 -0
  34. package/src/components/navigation/styles/_bar.scss +51 -0
  35. package/src/components/navigation/styles/_base.scss +129 -0
  36. package/src/components/navigation/styles/_drawer.scss +169 -0
  37. package/src/components/navigation/styles/_rail.scss +65 -0
  38. package/src/components/navigation/styles.scss +6 -0
  39. package/src/components/snackbar/api.js +125 -0
  40. package/src/components/snackbar/constants.js +41 -0
  41. package/src/components/snackbar/features.js +69 -0
  42. package/src/components/snackbar/index.js +2 -0
  43. package/src/components/snackbar/position.js +63 -0
  44. package/src/components/snackbar/queue.js +74 -0
  45. package/src/components/snackbar/snackbar.js +70 -0
  46. package/src/components/snackbar/styles.scss +182 -0
  47. package/src/components/switch/api.js +44 -0
  48. package/src/components/switch/constants.js +80 -0
  49. package/src/components/switch/index.js +2 -0
  50. package/src/components/switch/styles.scss +172 -0
  51. package/src/components/switch/switch.js +71 -0
  52. package/src/components/textfield/api.js +49 -0
  53. package/src/components/textfield/constants.js +81 -0
  54. package/src/components/textfield/index.js +2 -0
  55. package/src/components/textfield/styles/base.scss +107 -0
  56. package/src/components/textfield/styles/filled.scss +58 -0
  57. package/src/components/textfield/styles/outlined.scss +66 -0
  58. package/src/components/textfield/styles.scss +6 -0
  59. package/src/components/textfield/textfield.js +68 -0
  60. package/src/core/build/constants.js +51 -0
  61. package/src/core/build/icon.js +78 -0
  62. package/src/core/build/ripple.js +92 -0
  63. package/src/core/build/text.js +54 -0
  64. package/src/core/collection/adapters/base.js +26 -0
  65. package/src/core/collection/adapters/mongodb.js +232 -0
  66. package/src/core/collection/adapters/route.js +201 -0
  67. package/src/core/collection/collection.js +259 -0
  68. package/src/core/collection/list-manager.js +157 -0
  69. package/src/core/compose/base.js +8 -0
  70. package/src/core/compose/component.js +225 -0
  71. package/src/core/compose/features/checkable.js +114 -0
  72. package/src/core/compose/features/disabled.js +25 -0
  73. package/src/core/compose/features/events.js +48 -0
  74. package/src/core/compose/features/icon.js +33 -0
  75. package/src/core/compose/features/index.js +20 -0
  76. package/src/core/compose/features/input.js +92 -0
  77. package/src/core/compose/features/lifecycle.js +69 -0
  78. package/src/core/compose/features/position.js +60 -0
  79. package/src/core/compose/features/ripple.js +32 -0
  80. package/src/core/compose/features/size.js +9 -0
  81. package/src/core/compose/features/style.js +12 -0
  82. package/src/core/compose/features/text.js +17 -0
  83. package/src/core/compose/features/textinput.js +118 -0
  84. package/src/core/compose/features/textlabel.js +28 -0
  85. package/src/core/compose/features/track.js +49 -0
  86. package/src/core/compose/features/variant.js +9 -0
  87. package/src/core/compose/features/withEvents.js +67 -0
  88. package/src/core/compose/index.js +16 -0
  89. package/src/core/compose/pipe.js +69 -0
  90. package/src/core/config.js +140 -0
  91. package/src/core/dom/attributes.js +33 -0
  92. package/src/core/dom/classes.js +70 -0
  93. package/src/core/dom/create.js +133 -0
  94. package/src/core/dom/events.js +175 -0
  95. package/src/core/dom/index.js +5 -0
  96. package/src/core/dom/utils.js +22 -0
  97. package/src/core/index.js +23 -0
  98. package/src/core/layout/index.js +93 -0
  99. package/src/core/state/disabled.js +14 -0
  100. package/src/core/state/emitter.js +63 -0
  101. package/src/core/state/events.js +29 -0
  102. package/src/core/state/index.js +6 -0
  103. package/src/core/state/lifecycle.js +64 -0
  104. package/src/core/state/store.js +112 -0
  105. package/src/core/utils/index.js +39 -0
  106. package/src/core/utils/mobile.js +74 -0
  107. package/src/core/utils/object.js +22 -0
  108. package/src/core/utils/validate.js +37 -0
  109. package/src/index.js +11 -0
  110. package/src/styles/abstract/_base.scss +2 -0
  111. package/src/styles/abstract/_config.scss +28 -0
  112. package/src/styles/abstract/_functions.scss +124 -0
  113. package/src/styles/abstract/_mixins.scss +261 -0
  114. package/src/styles/abstract/_variables.scss +158 -0
  115. package/src/styles/main.scss +78 -0
  116. package/src/styles/themes/_base-theme.scss +49 -0
  117. package/src/styles/themes/_baseline.scss +90 -0
  118. package/src/styles/themes/_forest.scss +71 -0
  119. package/src/styles/themes/_index.scss +6 -0
  120. package/src/styles/themes/_ocean.scss +71 -0
  121. package/src/styles/themes/_sunset.scss +55 -0
@@ -0,0 +1,14 @@
1
+ // src/core/state/disabled.js
2
+ export const createDisabled = (element) => {
3
+ return {
4
+ enable() {
5
+ element.disabled = false
6
+ return this
7
+ },
8
+
9
+ disable() {
10
+ element.disabled = true
11
+ return this
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,63 @@
1
+ // src/core/state/emitter.js
2
+ /**
3
+ * @module core/state
4
+ */
5
+
6
+ /**
7
+ * Creates an event emitter with subscription management
8
+ * @memberof module:core/state
9
+ * @function createEmitter
10
+ * @returns {Object} Event emitter interface
11
+ * @property {Function} on - Subscribe to an event
12
+ * @property {Function} off - Unsubscribe from an event
13
+ * @property {Function} emit - Emit an event
14
+ * @property {Function} clear - Clear all subscriptions
15
+ * @example
16
+ * const emitter = createEmitter()
17
+ * const unsubscribe = emitter.on('change', (data) => console.log(data))
18
+ * emitter.emit('change', { value: 42 })
19
+ * unsubscribe()
20
+ */
21
+ export const createEmitter = () => {
22
+ const events = new Map()
23
+
24
+ return {
25
+ /**
26
+ * Subscribe to an event
27
+ * @param {string} event - Event name
28
+ * @param {Function} callback - Event handler
29
+ * @returns {Function} Unsubscribe function
30
+ */
31
+ on: (event, callback) => {
32
+ const callbacks = events.get(event) || []
33
+ events.set(event, [...callbacks, callback])
34
+
35
+ return () => {
36
+ const callbacks = events.get(event) || []
37
+ events.set(event, callbacks.filter(cb => cb !== callback))
38
+ }
39
+ },
40
+
41
+ off (event, callback) {
42
+ const callbacks = events.get(event) || []
43
+ events.set(event, callbacks.filter(cb => cb !== callback))
44
+ },
45
+
46
+ /**
47
+ * Emit an event
48
+ * @param {string} event - Event name
49
+ * @param {...any} args - Event arguments
50
+ */
51
+ emit: (event, ...args) => {
52
+ const callbacks = events.get(event) || []
53
+ callbacks.forEach(cb => cb(...args))
54
+ },
55
+
56
+ /**
57
+ * Clear all event listeners
58
+ */
59
+ clear: () => {
60
+ events.clear()
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,29 @@
1
+ // src/core/state/events.js
2
+ export const createEventManager = (element) => {
3
+ const handlers = new Map()
4
+
5
+ return {
6
+ on (event, handler) {
7
+ element.addEventListener(event, handler)
8
+ handlers.set(handler, event)
9
+ return this
10
+ },
11
+
12
+ off (event, handler) {
13
+ element.removeEventListener(event, handler)
14
+ handlers.delete(handler)
15
+ return this
16
+ },
17
+
18
+ destroy () {
19
+ handlers.forEach((event, handler) => {
20
+ element.removeEventListener(event, handler)
21
+ })
22
+ handlers.clear()
23
+ },
24
+
25
+ getHandlers () {
26
+ return handlers
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,6 @@
1
+ // src/core/state/index.js
2
+ export { createEmitter } from './state/emitter'
3
+ export { createStore } from './store'
4
+ export { createLifecycle } from './lifecycle'
5
+ export { createDisabled } from './disabled'
6
+ export { createEventManager } from './events'
@@ -0,0 +1,64 @@
1
+ // src/core/state/lifecycle.js
2
+ import { createEmitter } from './emitter'
3
+
4
+ export const createLifecycle = (element, managers = {}) => {
5
+ let mounted = false
6
+ const emitter = createEmitter()
7
+
8
+ return {
9
+ // Mount/Unmount state management
10
+ onMount: (handler) => emitter.on('mount', handler),
11
+ onUnmount: (handler) => emitter.on('unmount', handler),
12
+
13
+ mount: () => {
14
+ if (!mounted) {
15
+ mounted = true
16
+ emitter.emit('mount')
17
+ }
18
+ },
19
+
20
+ unmount: () => {
21
+ if (mounted) {
22
+ mounted = false
23
+ emitter.emit('unmount')
24
+ emitter.clear()
25
+ }
26
+ },
27
+
28
+ isMounted: () => mounted,
29
+
30
+ // Cleanup and destruction
31
+ destroy() {
32
+ // First trigger unmount
33
+ if (mounted) {
34
+ this.unmount()
35
+ }
36
+
37
+ // Clean up all event listeners
38
+ if (managers.events) {
39
+ managers.events.destroy()
40
+ }
41
+
42
+ // Clean up text element
43
+ if (managers.text) {
44
+ const textElement = managers.text.getElement()
45
+ if (textElement) {
46
+ textElement.remove()
47
+ }
48
+ }
49
+
50
+ // Clean up icon element
51
+ if (managers.icon) {
52
+ const iconElement = managers.icon.getElement()
53
+ if (iconElement) {
54
+ iconElement.remove()
55
+ }
56
+ }
57
+
58
+ // Remove the main element
59
+ if (element) {
60
+ element.remove()
61
+ }
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,112 @@
1
+ // src/core/state/store.js
2
+ import { createEmitter } from './emitter'
3
+
4
+ /**
5
+ * Creates a state store with support for derived state and middleware
6
+ * @template T
7
+ * @param {T} initialState - Initial state object
8
+ * @param {Object} [options] - Store options
9
+ * @param {Function[]} [options.middleware] - Middleware functions
10
+ * @returns {Object} State store interface
11
+ */
12
+ export const createStore = (initialState = {}, options = {}) => {
13
+ let state = { ...initialState }
14
+ const emitter = createEmitter()
15
+ const derivedStates = new Map()
16
+ const middleware = options.middleware || []
17
+
18
+ const notifyListeners = (newState, oldState) => {
19
+ emitter.emit('change', newState, oldState)
20
+ }
21
+
22
+ const applyMiddleware = (newState, oldState) => {
23
+ return middleware.reduce((state, fn) => fn(state, oldState), newState)
24
+ }
25
+
26
+ return {
27
+ /**
28
+ * Get current state including derived values
29
+ * @returns {T} Current state
30
+ */
31
+ getState: () => {
32
+ const derivedValues = {}
33
+ derivedStates.forEach((compute, key) => {
34
+ derivedValues[key] = compute(state)
35
+ })
36
+ return { ...state, ...derivedValues }
37
+ },
38
+
39
+ /**
40
+ * Update state
41
+ * @param {Object|Function} update - State update or updater function
42
+ */
43
+ setState: (update) => {
44
+ const oldState = { ...state }
45
+ const newState = typeof update === 'function'
46
+ ? update(state)
47
+ : { ...state, ...update }
48
+
49
+ state = applyMiddleware(newState, oldState)
50
+ notifyListeners(state, oldState)
51
+ },
52
+
53
+ /**
54
+ * Subscribe to state changes
55
+ * @param {Function} listener - Change listener
56
+ * @returns {Function} Unsubscribe function
57
+ */
58
+ subscribe: (listener) => emitter.on('change', listener),
59
+
60
+ /**
61
+ * Create a derived state value
62
+ * @param {string} key - Derived state key
63
+ * @param {Function} computation - Function to compute derived value
64
+ * @returns {Function} Function to remove derived state
65
+ */
66
+ derive: (key, computation) => {
67
+ derivedStates.set(key, computation)
68
+ return () => {
69
+ derivedStates.delete(key)
70
+ }
71
+ },
72
+
73
+ /**
74
+ * Select a specific slice of state
75
+ * @param {Function} selector - State selector function
76
+ * @returns {any} Selected state
77
+ */
78
+ select: (selector) => selector(state),
79
+
80
+ /**
81
+ * Reset state to initial values
82
+ */
83
+ reset: () => {
84
+ state = { ...initialState }
85
+ notifyListeners(state, null)
86
+ }
87
+ }
88
+ }
89
+
90
+ // Example middleware
91
+ export const loggingMiddleware = (newState, oldState) => {
92
+ console.log('State change:', {
93
+ old: oldState,
94
+ new: newState,
95
+ diff: Object.keys(newState).reduce((acc, key) => {
96
+ if (newState[key] !== oldState[key]) {
97
+ acc[key] = { from: oldState[key], to: newState[key] }
98
+ }
99
+ return acc
100
+ }, {})
101
+ })
102
+ return newState
103
+ }
104
+
105
+ // Example derived state
106
+ export const deriveFiltered = (predicate) => (state) =>
107
+ Object.keys(state).reduce((acc, key) => {
108
+ if (predicate(state[key], key)) {
109
+ acc[key] = state[key]
110
+ }
111
+ return acc
112
+ }, {})
@@ -0,0 +1,39 @@
1
+ // src/core/utils/index.js
2
+
3
+ export { isObject, byString } from './object'
4
+
5
+ /**
6
+ * Normalizes class names by handling various input formats
7
+ * @param {...(string|string[])} classes - Classes to normalize
8
+ * @returns {string[]} Array of unique, non-empty class names
9
+ */
10
+ export const normalizeClasses = (...classes) => {
11
+ return [...new Set(
12
+ classes
13
+ .flat()
14
+ .reduce((acc, cls) => {
15
+ if (typeof cls === 'string') {
16
+ // Split space-separated classes and add them individually
17
+ acc.push(...cls.split(/\s+/))
18
+ }
19
+ return acc
20
+ }, [])
21
+ .filter(Boolean) // Remove empty strings
22
+ )]
23
+ }
24
+
25
+ /**
26
+ * Creates a transformer that only runs if a condition is met
27
+ * @param {Function} predicate - Condition to check
28
+ * @param {Function} transformer - Transformer to run if condition is true
29
+ * @returns {Function} Conditional transformer
30
+ */
31
+ export const when = (predicate, transformer) => (obj, context) =>
32
+ predicate(obj, context) ? transformer(obj, context) : obj
33
+
34
+ /**
35
+ * Joins class names, filtering out falsy values
36
+ * @param {...string} classes - Class names to join
37
+ * @returns {string} Joined class names
38
+ */
39
+ export const classNames = (...classes) => classes.filter(Boolean).join(' ')
@@ -0,0 +1,74 @@
1
+ // src/core/utils/mobile.js
2
+
3
+ /**
4
+ * Mobile device detection and capability checks
5
+ * This provides a centralized way to handle mobile-specific features and behaviors
6
+ */
7
+
8
+ /**
9
+ * Detects if the current device is likely a mobile device
10
+ * Uses a combination of user agent and screen size checks for reliability
11
+ */
12
+ export const isMobileDevice = () => {
13
+ const userAgent = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
14
+ navigator.userAgent
15
+ )
16
+ const screenSize = window.innerWidth <= 768
17
+ return userAgent || screenSize
18
+ }
19
+
20
+ export const PASSIVE_EVENTS = { passive: true }
21
+
22
+ /**
23
+ * Configuration constants for mobile interactions
24
+ * Following WCAG guidelines and touch interaction best practices
25
+ */
26
+ export const TOUCH_CONFIG = {
27
+ TARGET_SIZE: 44, // Minimum touch target size in pixels (WCAG standard)
28
+ FEEDBACK_DURATION: 200, // Duration of touch feedback animation in ms
29
+ TAP_THRESHOLD: 250, // Maximum duration for a touch to be considered a tap
30
+ SWIPE_THRESHOLD: 50 // Minimum distance for a touch to be considered a swipe
31
+ }
32
+
33
+ /**
34
+ * Accessibility-minded touch target sizes
35
+ * Based on WCAG guidelines for touch targets
36
+ */
37
+ export const TOUCH_TARGETS = {
38
+ MINIMUM: 44, // Minimum recommended size in pixels
39
+ COMFORTABLE: 48, // Comfortable touch target size
40
+ LARGE: 56 // Large touch target for primary actions
41
+ }
42
+
43
+ /**
44
+ * Default passive event configuration for touch events
45
+ * Improves scroll performance on mobile devices
46
+ */
47
+
48
+ /**
49
+ * Detects if the current device supports touch events
50
+ */
51
+ export const hasTouchSupport = () => {
52
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0
53
+ }
54
+
55
+ /**
56
+ * Normalizes both mouse and touch events into a consistent format
57
+ * This allows components to handle interactions uniformly
58
+ */
59
+ export const normalizeEvent = (event) => {
60
+ if (event.touches) {
61
+ const touch = event.touches[0]
62
+ return {
63
+ clientX: touch.clientX,
64
+ clientY: touch.clientY,
65
+ pageX: touch.pageX,
66
+ pageY: touch.pageY,
67
+ target: event.target,
68
+ preventDefault: () => event.preventDefault(),
69
+ stopPropagation: () => event.stopPropagation(),
70
+ type: event.type
71
+ }
72
+ }
73
+ return event
74
+ }
@@ -0,0 +1,22 @@
1
+ const isObject = (object) => {
2
+ return object &&
3
+ typeof object === 'object' &&
4
+ Object.getPrototypeOf(object) === Object.getPrototypeOf({})
5
+ }
6
+
7
+ const byString = (o, s) => {
8
+ s = s.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
9
+ s = s.replace(/^\./, '') // strip a leading dot
10
+ const a = s.split('.')
11
+ for (let i = 0, n = a.length; i < n; ++i) {
12
+ const k = a[i]
13
+ if (k in o) {
14
+ o = o[k]
15
+ } else {
16
+ return
17
+ }
18
+ }
19
+ return o
20
+ }
21
+
22
+ export { isObject, byString }
@@ -0,0 +1,37 @@
1
+ // src/core/utils/validate.js
2
+
3
+ /**
4
+ * Validates configuration object against schema
5
+ * @param {Object} config - Configuration to validate
6
+ * @param {Object} schema - Validation schema
7
+ * @throws {Error} If validation fails
8
+ */
9
+ export const validateConfig = (config, schema) => {
10
+ const errors = []
11
+
12
+ Object.entries(schema).forEach(([key, rule]) => {
13
+ // Check required fields
14
+ if (rule.required && !config[key]) {
15
+ errors.push(`Missing required field: ${key}`)
16
+ }
17
+
18
+ // Check type if value exists
19
+ if (config[key] !== undefined && rule.type) {
20
+ const type = typeof config[key]
21
+ if (type !== rule.type) {
22
+ errors.push(`Invalid type for ${key}: expected ${rule.type}, got ${type}`)
23
+ }
24
+ }
25
+
26
+ // Check allowed values if specified
27
+ if (config[key] !== undefined && rule.enum) {
28
+ if (!rule.enum.includes(config[key])) {
29
+ errors.push(`Invalid value for ${key}. Must be one of: ${rule.enum.join(', ')}`)
30
+ }
31
+ }
32
+ })
33
+
34
+ if (errors.length > 0) {
35
+ throw new Error(`Configuration validation failed:\n${errors.join('\n')}`)
36
+ }
37
+ }
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // src/index.js
2
+ export { createElement } from './core/dom/create'
3
+ export { default as createLayout } from './core/layout'
4
+ export { default as createButton } from './components/button'
5
+ export { default as createCheckbox } from './components/checkbox'
6
+ export { default as createTextfield } from './components/textfield'
7
+ export { default as createSwitch } from './components/switch'
8
+ export { default as createContainer } from './components/container'
9
+ export { default as createSnackbar } from './components/snackbar'
10
+ export { default as createNavigation } from './components/navigation'
11
+ export { default as createList } from './components/list'
@@ -0,0 +1,2 @@
1
+ // src/styles/abstract/_base.scss
2
+ $prefix: 'mtrl';
@@ -0,0 +1,28 @@
1
+ // src/styles/abstract/_config.scss
2
+ @use 'variables' as *;
3
+ @forward 'variables';
4
+ @forward 'functions';
5
+ @forward 'mixins' show
6
+ typography,
7
+ state-layer,
8
+ elevation,
9
+ motion-transition,
10
+ motion-exit,
11
+ motion-enter,
12
+ focus-ring,
13
+ interactive-states,
14
+ flex-center,
15
+ center-content,
16
+ container,
17
+ truncate,
18
+ shape,
19
+ breakpoint-up,
20
+ breakpoint-down,
21
+ breakpoint-between,
22
+ visually-hidden,
23
+ reduced-motion,
24
+ high-contrast,
25
+ rtl,
26
+ touch-target,
27
+ custom-scrollbar,
28
+ print;
@@ -0,0 +1,124 @@
1
+ // src/styles/abstract/_functions.scss
2
+ @use 'sass:map';
3
+ @use 'sass:math';
4
+ @use 'sass:color';
5
+ @use 'variables' as v;
6
+
7
+ // Get nested map value
8
+ @function map-deep-get($map, $keys...) {
9
+ @each $key in $keys {
10
+ $map: map.get($map, $key);
11
+ }
12
+ @return $map;
13
+ }
14
+
15
+ // Get value from design tokens
16
+ @function get-token($category, $token) {
17
+ $token-map: null;
18
+
19
+ @if $category == 'state' {
20
+ $token-map: v.$state;
21
+ } @else if $category == 'motion' {
22
+ $token-map: v.$motion;
23
+ } @else if $category == 'elevation' {
24
+ $token-map: v.$elevation;
25
+ } @else if $category == 'typescale' {
26
+ $token-map: v.$typescale;
27
+ } @else if $category == 'shape' {
28
+ $token-map: v.$shape;
29
+ } @else if $category == 'z-index' {
30
+ $token-map: v.$z-index;
31
+ }
32
+
33
+ @if not $token-map {
34
+ @error 'Unknown token category: #{$category}';
35
+ }
36
+
37
+ $value: map.get($token-map, $token);
38
+
39
+ @if not $value {
40
+ @error 'Unknown token: #{$token} in category #{$category}';
41
+ }
42
+
43
+ @return $value;
44
+ }
45
+
46
+ // State Layer
47
+ @function get-state-opacity($state) {
48
+ @return get-token('state', '#{$state}-state-layer-opacity');
49
+ }
50
+
51
+ // Motion
52
+ @function get-motion-duration($duration) {
53
+ @return get-token('motion', 'duration-#{$duration}');
54
+ }
55
+
56
+ @function get-motion-easing($easing) {
57
+ @return get-token('motion', 'easing-#{$easing}');
58
+ }
59
+
60
+ // Elevation
61
+ @function get-elevation($level) {
62
+ @return get-token('elevation', 'level-#{$level}');
63
+ }
64
+
65
+ // Typography
66
+ @function get-typography($scale) {
67
+ @return get-token('typescale', $scale);
68
+ }
69
+
70
+ // Shape
71
+ @function get-shape($size) {
72
+ @return get-token('shape', $size);
73
+ }
74
+
75
+ // Z-index
76
+ @function get-z-index($layer) {
77
+ @return get-token('z-index', $layer);
78
+ }
79
+
80
+ // Breakpoints
81
+ @function get-breakpoint($size) {
82
+ @return map.get(v.$breakpoints, $size);
83
+ }
84
+
85
+ // Color utilities
86
+ @function get-luminance($color) {
87
+ $red: color.red($color);
88
+ $green: color.green($color);
89
+ $blue: color.blue($color);
90
+
91
+ $luminance: ($red * 0.299 + $green * 0.587 + $blue * 0.114) / 255;
92
+ @return $luminance;
93
+ }
94
+
95
+ @function is-light-color($color) {
96
+ @return get-luminance($color) > 0.6;
97
+ }
98
+
99
+ @function get-contrast-color($color, $light: #fff, $dark: #000) {
100
+ @return if(is-light-color($color), $dark, $light);
101
+ }
102
+
103
+ // Unit conversion utilities
104
+ @function strip-unit($number) {
105
+ @if type-of($number) == 'number' and not unitless($number) {
106
+ @return math.div($number, ($number * 0 + 1));
107
+ }
108
+ @return $number;
109
+ }
110
+
111
+ @function to-rem($size, $base-size: 16px) {
112
+ $rem-size: math.div(strip-unit($size), strip-unit($base-size));
113
+ @return #{$rem-size}rem;
114
+ }
115
+
116
+ @function to-em($size, $base-size: 16px) {
117
+ $em-size: math.div(strip-unit($size), strip-unit($base-size));
118
+ @return #{$em-size}em;
119
+ }
120
+
121
+ // Value constraint utility
122
+ @function constrain($min, $value, $max) {
123
+ @return max($min, min($value, $max));
124
+ }